近期整理了一下高频的前端面试题,分享给咱们一起来学习。如有问题,欢迎纠正!

前端面试题系列文章:

【1】2022高频前端面试题——HTML篇

【2】2022高频前端面试题——CSS篇

JavaScript面试题汇总(上篇)

1. 依据下面ES6结构函数的书写办法,要求写出ES5

class Example {
  constructor(name) { 
    this.name = name;
  }
  init() { 
    const fun = () => { console.log(this.name) }
    fun(); 
  } 
}
const e = new Example('Hello');
e.init();

参考答案:

function Example(name) {
      'use strict';
      if (!new.target) {
           throw new TypeError('Class constructor cannot be invoked without new');
      }
      this.name = name;
}
Object.defineProperty(Example.prototype, 'init', {
      enumerable: false,
      value: function () {
           'use strict';
           if (new.target) {
               throw new TypeError('init is not a constructor');
           }
           var fun = function () {
               console.log(this.name);
           }
           fun.call(this);
      }
})

解析:

此题的要害在于是否清楚ES6class和一般结构函数的差异,记住它们有以下差异,就不会有遗漏:

  1. ES6中的class有必要经过new来调用,不能作为一般函数调用,不然报错

    因而,在答案中,参加了new.target来判别调用办法

  2. ES6class中的一切代码均处于严厉办法之下

因而,在答案中,无论是结构函数本身,仍是原型办法,都运用了严厉办法

  1. ES6中的原型办法是不行被枚举的

    因而,在答案中,界说原型办法运用了特色描绘符,让其不行枚举

  2. 原型上的办法不允许经过new来调用

    因而,在答案中,原型办法中参加了new.target来判别调用办法

2. 数组去重有哪些办法?(美团19年)

参考答案:

// 数字或字符串数组去重,功率高
function unique(arr) {
      var result = {}; // 运用方针特色名的仅有性来确保不重复
      for (var i = 0; i < arr.length; i++) {
           if (!result[arr[i]]) {
               result[arr[i]] = true;
           }
      }
      return Object.keys(result); // 获取方针一切特色名的数组
}
// 恣意数组去重,适配规模光,功率低
function unique(arr) {
      var result = []; // 成果数组
      for (var i = 0; i < arr.length; i++) {
           if (!result.includes(arr[i])) {
               result.push(arr[i]);
           }
      }
      return result;
}
// 运用ES6的Set去重,适配规模广,功率一般,书写简略
function unique(arr) {
      return [...new Set(arr)]
}

3. 描绘下列代码的履行成果

foo(typeof a);
function foo(p) {
    console.log(this);
    console.log(p);
    console.log(typeof b);
    let b = 0;
}

参考答案:

报错,报错的方位在console.log(typeof b);

报错原因:ReferenceError: Cannot access ‘b’ before initialization

解析:

这道题调查的是ES6新增的声明变量要害字let以及暂时性死区的常识。let和曾经的var要害字不相同,无法在let声明变量之前拜访到该变量,所以在typeof b的当地就会报错。

4. 描绘下列代码的履行成果

class Foo {
    constructor(arr) { 
        this.arr = arr; 
    }
    bar(n) {
        return this.arr.slice(0, n);
    }
}
var f = new Foo([0, 1, 2, 3]);
console.log(f.bar(1));
console.log(f.bar(2).splice(1, 1));
console.log(f.arr);

参考答案:

[ 0 ]
[ 1 ]
[ 0, 1, 2, 3 ]

解析:

首要调查的是数组相关的常识。f方针上面有一个特色arrarr的值在初始化的时分会被初始化为 [0, 1, 2, 3] ,之后就彻底是调查数组以及数组办法的运用了。

5. 描绘下列代码的履行成果

01 function f(count) {
02    console.log(`foo${count}`);
03    setTimeout(() => { console.log(`bar${count}`); });
04 }
05 f(1);
06 f(2);
07 setTimeout(() => { f(3); });

参考答案:

foo1
foo2
bar1
bar2
foo3
bar3

解析:

这个彻底是调查的异步的常识。调用f(1) 的时分,会履行同步代码,打印出foo1,然后03行的setTimeout被放入到异步履行行列,接下来调用f(2) 的时分,打印出foo2,后边03行的setTimeout又被放入到异步履行行列。然后履行07行的句子,被放入到异步履行行列。至此,一切同步代码就都履行完毕了。

接下来开始履行异步代码,那么咱们时刻没写,就都是相同的,所以谁先被放入到异步行列,谁就先履行,所以先打印出bar1、然后是bar2,接下来履行之前07行放入到异步行列里边的setTimeout,先履行f函数里边的同步代码,打印出foo3,然后是最终一个异步,打印出bar3

6. 描绘下列代码的履行成果

var a = 2;
var b = 5;
console.log(a === 2 || 1 && b === 3 || 4);

参考答案:

true

调查的是逻辑运算符。在 || 里边,只需有一个为真,后边的直接短路,都不必去计算。所以a === 2得到true之后直接短路了,回来true

7. 描绘下列代码的履行成果

export class ButtonWrapper {
    constructor(domBtnEl, hash) {
        this.domBtnEl = domBtnEl;
        this.hash = hash;
        this.bindEvent();
    }
    bindEvent() {
        this.domBtnEl.addEventListener('click', this.clickEvent, false);
    }
    detachEvent() {
        this.domBtnEl.removeEventListener('click', this.clickEvent);
    }
    clickEvent() {
        console.log(`The hash of the button is: ${this.hash}`);
    }
}

参考答案:

上面的代码导出了一个ButtonWrapper类,该类在被实例化的时分,实例化方针上面有两个特色,别离是domBtnElhashdomBtnEl是一个DOM节点,之后为这个domBtnEl绑定了点击事情,点击后打印出The hash of the button is: hash那句话。detachEvent是移除点击事情,当调用实例化方针的detachEvent办法时,点击事情就会被移除。

8. 箭头函数有哪些特色

参考答案:

  1. 更简练的语法,例如

    • 只需一个形参就不需求用括号括起来
    • 假设函数体只需一行,就不需求放到一个块中
    • 假设return句子是函数体内仅有的句子,就不需求return要害字
  2. 箭头函数没有自己的thisargumentssuper

  3. 箭头函数this只会从自己的效果域链的上一层承继this

9. 说一说类的承继

参考答案:

承继是面向方针编程中的三大特性之一。

JavaScript中的承继经过不断的开展,从开始的方针假充渐渐开展到了今日的圣杯办法承继。

其间最需求把握的便是伪经典承继圣杯办法的承继。

很长一段时刻,JS 承继运用的都是组合承继。这种承继也被称之为伪经典承继,该承继办法综合了原型链和盗用结构函数的办法,将两者的长处集中了起来。

组合承继弥补了之前原型链和盗用结构函数这两种办法各自的缺乏,是JavaScript中运用最多的承继办法。

组合承继最大的问题便是功率问题。最首要便是父类的结构函数一向会被调用两次:一次是在创立子类原型时调用,另一次是在子类结构函数中调用。

实质上,子类原型最终是要包括超类方针的一切实例特色,子类结构函数只需在履行时重写自己的原型就行了。

圣杯办法的承继处理了这一问题,其根本思路便是不经过调用父类结构函数来给子类原型赋值,而是获得父类原型的一个副本,然后将回来的新方针赋值给子类原型。

解析:该题首要调查便是对js中的承继是否了解,以及常见的承继的办法有哪些。最常用的承继便是组合承继(伪经典承继)和圣杯办法承继。下面附上js中这两种承继办法的详细解析。

下面是一个组合承继的比方:

// 基类
var Person = function (name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.test = "this is a test";
Person.prototype.testFunc = function () {
    console.log('this is a testFunc');
}
// 子类
var Student = function (name, age, gender, score) {
    Person.apply(this, [name, age]); // 盗用结构函数
    this.gender = gender;
    this.score = score;
}
Student.prototype = new Person(); // 改动 Student 结构函数的原型方针
Student.prototype.testStuFunc = function () {
    console.log('this is a testStuFunc');
}
// 测验
var zhangsan = new Student("张三", 18, "男", 100);
console.log(zhangsan.name); // 张三
console.log(zhangsan.age); // 18
console.log(zhangsan.gender); // 男
console.log(zhangsan.score); // 100
console.log(zhangsan.test); // this is a test
zhangsan.testFunc(); // this is a testFunc
zhangsan.testStuFunc(); // this is a testStuFunc

在上面的比方中,咱们运用了组合承继的办法来完结承继,能够看到无论是基类上面的特色和办法,仍是子类自己的特色和办法,都得到了很好的完结。

可是在组合承继中存在功率问题,比方在上面的代码中,咱们其实调用了两次Person,产生了两组nameage特色,一组在原型上,一组在实例上。

也便是说,咱们在履行Student.prototype = new Person( ) 的时分,咱们是想要Person原型上面的办法,特色是不需求的,因为特色之后能够经过Person.apply(this, [name, age]) 拿到,可是当你new Person( ) 的时分,会实例化一个Person方针出来,这个方针上面,特色和办法都有。

圣杯办法的承继处理了这一问题,其根本思路便是不经过调用父类结构函数来给子类原型赋值,而是获得父类原型的一个副本,然后将回来的新方针赋值给子类原型。

下面是一个圣杯办法的示例:

// target 是子类,origin 是基类
// target ---> Student, origin ---> Person
function inherit(target, origin) {
    function F() { }; // 没有任何剩下的特色
    // origin.prototype === Person.prototype, origin.prototype.constructor === Person 结构函数
    F.prototype = origin.prototype;
    // 假设 new F() 出来的方针叫小 f
    // 那么这个 f 的原型方针 === F.prototype === Person.prototype
    // 那么 f.constructor === Person.prototype.constructor === Person 的结构函数
    target.prototype = new F();
    // 而 f 这个方针又是 target 方针的原型方针
    // 这意味着 target.prototype.constructor === f.constructor
    // 所以 target 的 constructor 会指向 Person 结构函数
    // 咱们要让子类的 constructor 从头指向自己
    // 若不修正则会发现 constructor 指向的是父类的结构函数
    target.prototype.constructor = target;
}
// 基类
var Person = function (name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.test = "this is a test";
Person.prototype.testFunc = function () {
    console.log('this is a testFunc');
}
// 子类
var Student = function (name, age, gender, score) {
    Person.apply(this, [name, age]);
    this.gender = gender;
    this.score = score;
}
inherit(Student, Person); // 运用圣杯办法完结承继
// 在子类上面增加办法
Student.prototype.testStuFunc = function () {
    console.log('this is a testStuFunc');
}
// 测验
var zhangsan = new Student("张三", 18, "男", 100);
console.log(zhangsan.name); // 张三
console.log(zhangsan.age); // 18
console.log(zhangsan.gender); // 男
console.log(zhangsan.score); // 100
console.log(zhangsan.test); // this is a test
zhangsan.testFunc(); // this is a testFunc
zhangsan.testStuFunc(); // this is a testStuFunc

在上面的代码中,咱们在inherit办法中创立了一个中间层,之后让F的原型和父类的原型指向同一地址,再让子类的原型指向这个F的实例化方针来完结了承继。

这样咱们的承继,特色就不会像之前那样实例方针上一份,原型方针上一份,具有两份。圣杯办法承继是目前js承继的最优解。

最终我再画个图帮助咱们了解,如下图:

组合办法(伪经典办法)下的承继示意图:

2022高频前端面试题合集之JavaScript篇(上)

圣杯办法下的承继示意图:

2022高频前端面试题合集之JavaScript篇(上)

10.new操作符都做了哪些事?

参考答案:

new运算符创立一个用户界说的方针类型的实例或具有结构函数的内置方针的实例。

new要害字会进行如下的操作:
进程1:创立一个空的简略JavaScript方针,即 { } ;
进程2:链接该方针到另一个方针(即设置该方针的原型方针);
进程3:将进程1新创立的方针作为this的上下文;
进程4:假设该函数没有回来方针,则回来this

11.call、apply、bind的差异 ?

参考答案:

callapply的功用相同,差异在于传参的办法不相同:

  • fn.call(obj, arg1, arg2, …) 调用一个函数, 具有一个指定的this值和别离地供给的参数(参数的列表)。
  • fn.apply(obj, [argsArray]) 调用一个函数,具有一个指定的this值,以及作为一个数组(或类数组方针)供给的参数。

bindcall/apply有一个很重要的差异,一个函数被call/apply的时分,会直接调用,可是bind会创立一个新函数。当这个新函数被调用时,bind( ) 的第一个参数将作为它运转时的this,之后的一序列参数将会在传递的实参前传入作为它的参数。

12. 事情循环机制(宏使命、微使命)

参考答案:

js中使命会分为同步使命和异步使命。

假设是同步使命,则会在主线程(也便是js引擎线程)上进行履行,构成一个履行栈。可是一旦遇到异步使命,则会将这些异步使命交给异步模块去处理,然后主线程持续履行后边的同步代码。

当异步使命有了运转成果今后,就会在使命行列里边放置一个事情,这个使命行列由事情触发线程来进行办理。

一旦履行栈中一切的同步使命履行完毕,就代表着当时的主线程(js引擎线程)闲暇了,系统就会读取使命行列,将能够运转的异步使命增加到履行栈中,开始履行。

js中,使命行列中的使命又能够被分为2种类型:宏使命(macrotask)与微使命(microtask

宏使命能够了解为每次履行栈所履行的代码便是一个宏使命,包括每次从事情行列中获取一个事情回调并放到履行栈中所履行的使命。

微使命能够了解为当时宏使命履行结束后当即履行的使命。

13. 你了解node中的事情循环机制吗?node11版别今后有什么改动

参考答案:

Node.js在主线程里维护了一个事情行列,当接到恳求后,就将该恳求作为一个事情放入这个行列中,然后持续接纳其他恳求。当主线程闲暇时(没有恳求接入时),就开始循环事情行列,检查行列中是否有要处理的事情,这时要分两种状况:假设对错I/O使命,就亲自处理,并经过回调函数回来到上层调用;假设是I/O使命,就从线程池中拿出一个线程来处理这个事情,并指定回调函数,然后持续循环行列中的其他事情。

当线程中的I/O使命完结今后,就履行指定的回调函数,并把这个完结的事情放到事情行列的尾部,等候事情循环,当主线程再次循环到该事情时,就直接处理并回来给上层调用。 这个进程就叫事情循环(Event Loop)。

无论是Linux渠道仍是Windows渠道,Node.js内部都是经过线程池来完结异步I/O操作的,而LIBUV针对不同渠道的差异性完结了统一调用。因而,Node.js的单线程仅仅是指JavaScript运转在单线程中,而并非Node.js是单线程。

Node.JS的事情循环分为6个阶段:

  • timers阶段:这个阶段履行timersetTimeout、setInterval)的回调
  • I/O callbacks阶段:处理一些上一轮循环中的少量未履行的I/O回调
  • idle、prepare阶段:仅Node.js内部运用
  • poll阶段:获取新的I/O事情, 恰当的条件下Node.js将堵塞在这儿
  • check阶段:履行setImmediate( ) 的回调
  • close callbacks阶段:履行socketclose事情回调

事情循环的履行次序为:

外部输入数据 –-> 轮询阶段(poll)-–> 检查阶段(check)-–> 关闭事情回调阶段(close callback)–-> 守时器检测阶段(timer)–->I/O事情回调阶段(I/O callbacks)-–>闲置阶段(idle、prepare)–->轮询阶段(依照该次序重复运转)…

浏览器Node.js环境下,微使命使命行列的履行时机不同

  • Node.js端,微使命在事情循环的各个阶段之间履行
  • 浏览器端,微使命在事情循环的宏使命履行完之后履行

Node.js v11.0.0版别于201810月,首要有以下变化:

  1. V8引擎更新至版别7.0
  2. http、httpstls模块默许运用WHESWG URL解析器。
  3. 躲藏子进程的操控台窗口默许改为了true
  4. FreeBSD 10不再支撑。
  5. 增加了多线程Worker Threads

14. 什么是函数柯里化?

参考答案:

柯里化(currying)又称部分求值。一个柯里化的函数首要会承受一些参数,承受了这些参数之后,该函数并不会当即求值,而是持续回来其他一个函数,方才传入的参数在函数构成的闭包中被保存起来。待到函数被真实需求求值的时分,之前传入的一切参数都会被一次性用于求值。

举个比方,便是把原本:

function(arg1,arg2) 变成function(arg1)(arg2)
function(arg1,arg2,arg3) 变成function(arg1)(arg2)(arg3)
function(arg1,arg2,arg3,arg4) 变成function(arg1)(arg2)(arg3)(arg4)

总而言之,便是将:

function(arg1,arg2,…,argn) 变成function(arg1)(arg2)…(argn)

15.promise.all办法的运用场景?数组中有必要每一项都是promise方针吗?不是promise方针会怎样处理 ?

参考答案:

*promise.all(promiseArray) *办法是promise方针上的静态办法,该办法的效果是将多个promise方针实例包装,生成并回来一个新的promise实例。

此办法在调集多个promise的回来成果时很有用。

回来值将会依照参数内的promise次序排列,而不是由调用promise的完结次序决议。

promise.all的特色

接纳一个Promise实例的数组或具有Iterator接口的方针

假设元素不是Promise方针,则运用Promise.resolve转成Promise方针

假设悉数成功,状况变为resolved,回来值将组成一个数组传给回调

只需有一个失利,状况就变为rejected,回来值将直接传递给回调 *all( )*的回来值,也是新的promise方针

16.this的指向哪几种 ?

参考答案:

总结起来,this的指向规矩有如下几条:

  • 在函数体中,非显式或隐式地简略调用函数时,在严厉办法下,函数内的this会被绑定到undefined上,在非严厉办法下则会被绑定到大局方针window/global上。
  • 一般运用new办法调用结构函数时,结构函数内的this会被绑定到新创立的方针上。
  • 一般经过call/apply/bind办法显式调用函数时,函数体内的this会被绑定到指定参数的方针上。
  • 一般经过上下文方针调用函数时,函数体内的this会被绑定到该方针上。
  • 在箭头函数中,this的指向是由外层(函数或大局)效果域来决议的。

17.JS中承继完结的几种办法

参考答案:

JS的承继跟着言语的开展,从最早的方针假充到现在的圣杯办法,涌现出了许多不同的承继办法。每一种新的承继办法都是对前一种承继办法缺乏的一种补充。

  1. 原型链承继

  • 要点:让新实例的原型等于父类的实例。

  • 特色:实例可承继的特色有:实例的结构函数的特色,父类结构函数特色,父类原型的特色。(新实例不会承继父类实例的特色!)

  • 缺陷:

    • 1、新实例无法向父类结构函数传参。

    • 2、承继单一。

    • 3、一切新实例都会同享父类实例的特色。(原型上的特色是同享的,一个实例修正了原型特色,另一个实例的原型特色也会被修正!)

  1. 借用结构函数承继

  • 要点:用call( )apply( ) 将父类结构函数引进子类函数(在子类函数中做了父类函数的自履行(仿制))

  • 特色:
    – 1、只承继了父类结构函数的特色,没有承继父类原型的特色。

    • 2、处理了原型链承继缺陷1、2、3。
    • 3、能够承继多个结构函数特色(call多个)。
    • 4、在子实例中可向父实例传参。
  • 缺陷:
    – 1、只能承继父类结构函数的特色。

    • 2、无法完结结构函数的复用。(每次用每次都要从头调用)

    • 3、每个新实例都有父类结构函数的副本,臃肿。

  1. 组合办法(又被称之为伪经典办法)

  • 要点:结合了两种办法的长处,传参和复用

  • 特色:
    – 1、能够承继父类原型上的特色,能够传参,可复用。
    – 2、每个新实例引进的结构函数特色是私有的。

  • 缺陷:调用了两次父类结构函数(耗内存),子类的结构函数会替代原型上的那个父类结构函数。

  1. 寄生组合式承继(圣杯办法)

  • 要点:修正了组合承继的问题

18. 什么是事情监听

参考答案:

首要需求差异清楚事情监听和事情监听器。

在绑定事情的时分,咱们需求对应的书写一个事情处理程序,来应对事情产生时的详细行为。

这个事情处理程序咱们也称之为事情监听器。

当事情绑定好后,程序就会对事情进行监听,当用户触发事情时,就会履行对应的事情处理程序。

关于事情监听,W3C标准中界说了3个事情阶段,顺次是捕获阶段、方针阶段、冒泡阶段。

  • 捕获阶段:在事情方针抵达事情方针之前,事情方针有必要从window经过方针的先人节点传达到事情方针。 这个阶段被咱们称之为捕获阶段。在这个阶段注册的事情监听器在事情抵达其方针前有必要先处理事情。
  • 方针阶段:事情方针抵达其事情方针。 这个阶段被咱们称为方针阶段。一旦事情方针抵达事情方针,该阶段的事情监听器就要对它进行处理。假设一个事情方针类型被标志为不能冒泡。那么对应的事情方针在抵达此阶段时就会终止传达。
  • 冒泡阶段:事情方针以一个与捕获阶段相反的方向从事情方针传达经过其先人节点传达到window。这个阶段被称之为冒泡阶段。在此阶段注册的事情监听器会对相应的冒泡事情进行处理。

19. 什么是js的闭包?有什么效果?

参考答案:

一个函数和对其周围状况(lexical environment,词法环境)的引证绑缚在一起(或许说函数被引证包围),这样的组合便是闭包closure)。也便是说,闭包让你能够在一个内层函数中拜访到其外层函数的效果域。在JavaScript中,每逢创立一个函数,闭包就会在函数创立的一起被创立出来。

闭包的用途:

  1. 匿名自履行函数
  2. 成果缓存
  3. 封装
  4. 完结类和承继

20. 事情托付以及冒泡原理

参考答案:

事情托付,又被称之为事情署理。在JavaScript中,增加到页面上的事情处理程序数量将直接联络到页面全体的运转功用。导致这一问题的原因是多方面的。

首要,每个函数都是方针,都会占用内存。内存中的方针越多,功用就越差。其次,有必要事前指定一切事情处理程序而导致的DOM拜访次数,会推迟整个页面的交互就绪时刻。

对事情处理程序过多问题的处理方案便是事情托付。

事情托付运用了事情冒泡,只指定一个事情处理程序,就能够办理某一类型的一切事情。例如,click事情会一向冒泡到document层次。也便是说,咱们能够为整个页面指定一个onclick事情处理程序,而不必给每个可单击的元素别离增加事情处理程序。

事情冒泡(event bubbling),是指事情开始时由最详细的元素(文档中嵌套层次最深的那个节点)接纳,然后逐级向上传达到较为不详细的节点(文档)。

21.let const var的差异?什么是块级效果域?怎样用?

参考答案:

  1. var界说的变量,没有块的概念,能够跨块拜访, 不能跨函数拜访,有变量提高。
  2. let界说的变量,只能在块效果域里拜访,不能跨块拜访,也不能跨函数拜访,无变量提高,不行以重复声明。
  3. const用来界说常量,运用时有必要初始化(即有必要赋值),只能在块效果域里拜访,而且不能修正,无变量提高,不行以重复声明。

开始在JS中效果域有:大局效果域、函数效果域。没有块效果域的概念。

ES6中新增了块级效果域。块效果域由 { } 包括,if句子和for句子里边的 { } 也归于块效果域。

在曾经没有块效果域的时分,在 if 或许 for 循环中声明的变量会走漏成大局变量,其次便是 { } 中的内层变量或许会掩盖外层变量。块级效果域的出现处理了这些问题。

22.ES5的办法完结块级效果域(当即履行函数)ES6呢?

参考答案:

ES6原生支撑块级效果域。块效果域由 { } 包括,if句子和for句子里边的 { } 也归于块效果域。

运用let声明的变量或许运用const声明的常量,只能在块效果域里拜访,不能跨块拜访。

23.ES6箭头函数的特性

参考答案:

  1. 更简练的语法,例如

    • 只需一个形参就不需求用括号括起来
    • 假设函数体只需一行,就不需求放到一个块中
    • 假设return句子是函数体内仅有的句子,就不需求return要害字
  2. 箭头函数没有自己的thisargumentssuper

  3. 箭头函数this只会从自己的效果域链的上一层承继this

24. 箭头函数与一般函数的差异 ?

参考答案:

  1. 外形不同。箭头函数运用箭头界说,一般函数中没有

  2. 一般函数能够有匿名函数,也能够有详细名函数,可是箭头函数都是匿名函数。

  3. **箭头函数不能用于结构函数,不能运用new,**一般函数能够用于结构函数,以此创立方针实例。

  4. **箭头函数中this的指向不同,**在一般函数中,this总是指向调用它的方针,假设用作结构函数,this指向创立的方针实例。
    箭头函数本身不创立this,也能够说箭头函数本身没有this,可是它在声明时能够捕获其所在上下文的this供自己运用。

  5. 每一个一般函数调用后都具有一个arguments方针,用来存储实践传递的参数。

    可是箭头函数并没有此方针。取而代之用rest参数来处理

  6. 箭头函数不能用于Generator函数,不能运用yeild要害字。

  7. 箭头函数不具有prototype原型方针。而一般函数具有prototype原型方针。

  8. 箭头函数不具有super

  9. 箭头函数不具有new.target

25.JS的根本数据类型有哪些?根本数据类型和引证数据类型的差异

参考答案:

JavaScript中,数据类型全体上来讲能够分为两大类:根本类型引证数据类型

根本数据类型,一共有6种:

stringsymbolnumberbooleanundefinednull

其间symbol类型是在ES6里边新增加的根本数据类型。

引证数据类型,就只需1种:

object

根本数据类型的值又被称之为原始值或简略值,而引证数据类型的值又被称之为杂乱值或引证值。

两者的差异在于:

原始值是表明JavaScript中可用的数据或信息的最底层办法或最简略办法。简略类型的值被称为原始值,是因为它们是不行细化的。

也便是说,数字是数字,字符是字符,布尔值是truefalsenullundefined便是nullundefined。这些值本身很简略,不行以再进行拆分。因为原始值的数据巨细是固定的,所以原始值的数据是存储于内存中的栈区里边的。

JavaScript中,方针便是一个引证值。因为方针能够向下拆分,拆分红多个简略值或许杂乱值。引证值在内存中的巨细是未知的,因为引证值能够包括任何值,而不是一个特定的已知值,所以引证值的数据都是存储于堆区里边。

最终总结一下两者的差异:

  1. 拜访办法

    • 原始值:拜访到的是值
    • 引证值:拜访到的是引证地址
  2. 比较办法

    • 原始值:比较的是值
    • 引证值:比较的是地址
  3. 动态特色

    • 原始值:无法增加动态特色
    • 引证值:能够增加动态特色
  4. 变量赋值

    • 原始值:赋值的是值
    • 引证值:赋值的是地址

26.NaN是什么的缩写

参考答案:

NaN的全称为Not a Number,表明非数,或许说不是一个数。尽管 NaN 表明非数,可是它却归于number类型。

NaN有两个特色:

  1. 任何触及NaN的操作都会回来NaN
  2. NaN和任何值都不持平,包括它自己本身

27.JS的效果域类型

参考答案:

JavaScript里边,效果域一共有 4 种:大局效果域,局部效果域、函数效果域以及eval效果域。

**大局效果域:**这个是默许的代码运转环境,一旦代码被载入,引擎最先进入的便是这个环境。

**局部效果域:**当运用let或许const声明变量时,这些变量在一对花括号中存在局部效果域,只能够在花括号内部进行拜访运用。

**函数效果域:**当进入到一个函数的时分,就会产生一个函数效果域。函数效果域里边所声明的变量只在函数中供给拜访运用。

**eval效果域:**当调用eval( ) 函数的时分,就会产生一个eval效果域。

28.undefined==null回来的成果是什么?undefinednull的差异在哪?

参考答案:

回来true

这两个值都表明“无”的意思。

通常状况下, 当咱们企图拜访某个不存在的或许没有赋值的变量时,就会得到一个undefined值。Javascript会主动将声明是没有进行初始化的变量设为undifined

null值表明空,null不能经过Javascript来主动赋值,也便是说有必要要咱们自己手动来给某个变量赋值为null

解析:

那么为什么JavaScript要设置两个表明”无”的值呢?这其实是前史原因。

1995JavaScript诞生时,开始像Java相同,只设置了null作为表明”无”的值。依据C言语的传统,null被设计成能够主动转为0

可是,JavaScript的设计者,觉得这样做还不行,首要有以下两个原因。

  1. null像在Java里相同,被当成一个方针。可是,JavaScript的数据类型分红原始类型(primitive)和合成类型(complex)两大类,作者觉得表明”无”的值最好不是方针。
  2. JavaScript的开始版别没有包括过错处理机制,产生数据类型不匹配时,往往是主动转化类型或许默默地失利。作者觉得,假设null主动转为0,很不简略发现过错。

因而,作者又设计了一个undefined

这儿留意:先有null后有undefined出来,undefined是为了填补之前的坑。

JavaScript的开始版别是这样差异的:

null是一个表明”无”的方针(空方针指针),转为数值时为0

典型用法是:

  • 作为函数的参数,表明该函数的参数不是方针。
  • 作为方针原型链的结尾。

undefined是一个表明”无”的原始值,转为数值时为NaN

典型用法是:

  • 变量被声明晰,但没有赋值时,就等于undefined
  • 调用函数时,应该供给的参数没有供给,该参数等于undefined
  • 方针没有赋值的特色,该特色的值为undefined
  • 函数没有回来值时,默许回来undefined

29. 写一个函数判别变量类型

参考答案:

function getType(data){
    let type = typeof data;
    if(type !== "object"){
        return type
    }
    return Object.prototype.toString.call(data).replace(/^[object (S+)]$/,'$1')
}
function Person(){}
console.log(getType(1)); // number
console.log(getType(true)); // boolean
console.log(getType([1,2,3])); // Array
console.log(getType(/abc/)); // RegExp
console.log(getType(new Date)); // Date
console.log(getType(new Person)); // Object
console.log(getType({})); // Object

30.js的异步处理函数

参考答案:

在最前期的时分,JavaScript中要完结异步操作,运用的便是Callback回调函数。

可是回调函数会产生回调阴间(Callback Hell

之后 ES6 推出了Promise处理方案来处理回调阴间的问题。不过,尽管Promise作为ES6中供给的一种新的异步编程处理方案,可是它也有问题。比方,代码并没有因为新办法的出现而削减,反而变得更加杂乱,一起了解难度也加大。

之后,就出现了依据Generator的异步处理方案。不过,这种办法需求编写外部的履行器,而履行器的代码写起来一点也不简略。当然也能够运用一些插件,比方co模块来简化履行器的编写。

ES7提出的async函数,总算让JavaScript关于异步操作有了终极处理方案。

实践上,async仅仅生成器的一种语法糖而已,简化了外部履行器的代码,一起运用await替代yieldasync替代生成器的*号。

31.deferasync的差异

参考答案:

依照惯例,一切script元素都应该放在页面的head元素中。这种做法的目的便是把一切外部文件(CSS文件和JavaScript文件)的引证都放在相同的当地。可是,在文档的head元素中包括一切JavaScript文件,意味着有必要等到悉数JavaScript代码都被下载、解析和履行完结今后,才干开始出现页面的内容(浏览器在遇到body标签时才开始出现内容)。

关于那些需求许多JavaScript代码的页面来说,这无疑会导致浏览器在出现页面时出现明显的推迟,而推迟期间的浏览器窗口中将是一片空白。为了防止这个问题,现在Web运用程序一般都悉数JavaScript引证放在body元素中页面的内容后边。这样一来,在解析包括的JavaScript代码之前,页面的内容将彻底出现在浏览器中。而用户也会因为浏览器窗口显现空白页面的时刻缩短而感到打开页面的速度加快了。

有了deferasync后,这种局面得到了改善。

defer(推迟脚本)

推迟脚本:defer特色只适用于外部脚本文件。

假设给script标签界说了defer特色,这个特色的效果是表明脚本在履行时不会影响页面的结构。也便是说,脚本会被推迟到整个页面都解析完毕后再运转。因而,假设script元素中设置了defer特色,相当于告知浏览器当即下载,但推迟履行。

async(异步脚本)

异步脚本:async特色也只适用于外部脚本文件,并告知浏览器当即下载文件。

但与defer不同的是:符号为async的脚本并不确保依照指定它们的先后次序履行。

所以总结起来,两者之间最大的差异便是在于脚本下载完之后何时履行,显然defer是最接近咱们关于运用脚本加载和履行的要求的。

defer是当即下载但推迟履行,加载后续文档元素的进程将和脚本的加载并行进行(异步),可是脚本的履行要在一切元素解析完结之后,DOMContentLoaded事情触发之前完结。async是当即下载并履行,加载和烘托后续文档元素的进程将和js脚本的加载与履行并行进行(异步)。

32. 浏览器事情循环和使命行列

参考答案:

JavaScript的异步机制由事情循环和使命行列构成。

JavaScript本身是单线程言语,所谓异步依赖于浏览器或许操作系统等完结。JavaScript主线程具有一个履行栈以及一个使命行列,主线程会顺次履行代码,当遇到函数时,会先将函数入栈,函数运转完毕后再将该函数出栈,直到一切代码履行完毕。

遇到异步操作(例如:setTimeout、Ajax)时,异步操作会由浏览器(OS)履行,浏览器会在这些使命完结后,将事前界说的回调函数推入主线程的使命行列(task queue)中,当主线程的履行栈清空之后会读取使命行列中的回调函数,当使命行列被读取完毕之后,主线程接着履行,从而进入一个无限的循环,这便是事情循环。

33. 原型与原型链 (美团 19年)

参考答案:

  • 每个方针都有一个__proto__特色,该特色指向自己的原型方针
  • 每个结构函数都有一个prototype特色,该特色指向实例方针的原型方针
  • 原型方针里的constructor指向结构函数本身

如下图:

2022高频前端面试题合集之JavaScript篇(上)

每个方针都有自己的原型方针,而原型方针本身,也有自己的原型方针,从而构成了一条原型链条。

当企图拜访一个方针的特色时,它不仅仅在该方针上查找,还会查找该方针的原型,以及该方针的原型的原型,顺次层层向上查找,直到找到一个名字匹配的特色或抵达原型链的结尾。

34. 效果域与效果域链 (美团 19年)

参考答案:

效果域是在运转年代码中的某些特定部分中变量,函数和方针的可拜访性。换句话说,效果域决议了代码区块中变量和其他资源的可见性。ES6之前JavaScript没有块级效果域,只需大局效果域和函数效果域。ES6的到来,为咱们供给了块级效果域。

效果域链指的是效果域与效果域之间构成的链条。当咱们查找一个当时效果域没有界说的变量(自由变量)的时分,就会向上一级效果域寻找,假设上一级也没有,就再一层一层向上寻找,直到找到大局效果域仍是没找到,就宣告抛弃。这种一层一层的联络,便是效果域链 。

35. 闭包及运用场景以及闭包缺陷 (美团 19年)

参考答案:

闭包的运用场景:

  1. 匿名自履行函数
  2. 成果缓存
  3. 封装
  4. 完结类和承继

闭包的缺陷:

因为闭包的效果域链会引证包括它的函数的活动方针,导致这些活动方针不会被毁掉,因而会占用更多的内存。

36. 承继办法 (美团 19年)

参考答案:

参看前面第9题以及第18题答案。

37. 原始值与引证值 (美团 19年)

参考答案:

原始值是表明JavaScript中可用的数据或信息的最底层办法或最简略办法。简略类型的值被称为原始值,是因为它们是不行细化的。

也便是说,数字是数字,字符是字符,布尔值是truefalsenullundefined便是nullundefined。这些值本身很简略,不行以再进行拆分。因为原始值的数据巨细是固定的,所以原始值的数据是存储于内存中的栈区里边的。

JavaScript中,方针便是一个引证值。因为方针能够向下拆分,拆分红多个简略值或许杂乱值。引证值在内存中的巨细是未知的,因为引证值能够包括任何值,而不是一个特定的已知值,所以引证值的数据都是存储于堆区里边。

最终总结一下两者的差异:

  1. 拜访办法

    • 原始值:拜访到的是值
    • 引证值:拜访到的是引证地址
  2. 比较办法

    • 原始值:比较的是值
    • 引证值:比较的是地址
  3. 动态特色

    • 原始值:无法增加动态特色
    • 引证值:能够增加动态特色
  4. 变量赋值

    • 原始值:赋值的是值
    • 引证值:赋值的是地址

38. 描绘下列代码的履行成果

const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(1);
        }, 0);
        setTimeout(() => {
            console.log(2);
            resolve(3);
        }, 0)
        resolve(4);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg, 5); // 1 bb
    });
    setTimeout(() => {
        console.log(6);
    }, 0);
}))
first().then((arg) => {
    console.log(arg, 7); // 2 aa
    setTimeout(() => {
        console.log(8);
    }, 0);
});
setTimeout(() => {
    console.log(9);
}, 0);
console.log(10);

参考答案:

3
7
10
4 5
2 7
1
2
6
9
8

39. 怎样判别数组或方针(美团 19年)

参考答案:

  1. 经过instanceof进行判别
var arr = [1,2,3,1];
console.log(arr instanceof Array) // true
  1. 经过方针的constructor特色
var arr = [1,2,3,1];
console.log(arr.constructor === Array) // true
  1. Object.prototype.toString.call(arr)
console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
console.log(Object.prototype.toString.call([]));//[object Array]
  1. 能够经过ES6新供给的办法Array.isArray( )
Array.isArray([]) //true

40. 方针深仿制与浅仿制,独自问了Object.assign(美团 19年)

参考答案:

  • 浅仿制:仅仅仿制了根本类型的数据,而引证类型数据,仿制后也是会产生引证,咱们把这种仿制叫做浅仿制(浅仿制)

    浅仿制只仿制指向某个方针的指针,而不仿制方针本身,新旧方针仍是同享同一块内存。

  • 深仿制:在堆中从头分配内存,而且把源方针一切特色都进行新建仿制,以确保深仿制的方针的引证图不包括任何原有方针或方针图上的任何方针,仿制后的方针与本来的方针是彻底阻隔,互不影响。

Object.assign办法能够把恣意多个的源方针本身的可枚举特色仿制给方针方针,然后回来方针方针。可是Object.assign办法进行的是浅仿制,仿制的是方针的特色的引证,而不是方针本身。

42. 说说instanceof原理,并答复下面的标题(美团 19年)

function A(){}
function B(){}
A.prototype = new B(); 
let a = new A(); 
console.log(a instanceof B) // true of false ?

参考答案:

答案为true

instanceof原理:

instanceof用于检测一个方针是否为某个结构函数的实例。

例如:A instanceof B
instanceof用于检测方针A是不是B的实例,而检测是依据原型链进行查找的,也便是说Bprototype有没有在方针A的__proto__ 原型链上,假设有就回来true,不然回来false

43. 内存走漏(美团 19 年)

参考答案:

内存走漏(Memory Leak)是指程序中己动态分配的堆内存因为某种原因程序未开释或无法开释,形成系统内存的糟蹋,导致程序运转速度减慢乃至系统溃散等严重后果。

Javascript是一种高级言语,它不像C言语那样要手动恳求内存,然后手动开释,Javascript在声明变量的时分主动会分配内存,一般的类型比方number,一般放在栈内存里,方针放在堆内存里,声明一个变量,就分配一些内存,然后守时进行废物收回。废物收回的使命由JavaScript引擎中的废物收回器来完结,它监督一切方针,并删去那些不行拜访的方针。

根本的废物收回算法称为**“符号-铲除”**,定期履行以下“废物收回”进程:

  • 废物收回器获取根并**“符号”**(记住)它们。
  • 然后它拜访并“符号”一切来自它们的引证。
  • 然后它拜访符号的方针并符号它们的引证。一切被拜访的方针都被记住,以便今后不再拜访同一个方针两次。
  • 以此类推,直到有未拜访的引证(能够从根拜访)停止。
  • 除符号的方针外,一切方针都被删去。

44.ES6新增哪些东西?让你自己说(美团 19 年)

参考答案:

ES6新增内容许多,这儿列举出一些要害的以及平常常用的新增内容:

  1. 箭头函数
  2. 字符串模板
  3. 支撑模块化(import、export
  4. 类(class、constructor、extends
  5. let、const要害字
  6. 新增一些数组、字符串等内置结构函数办法,例如Array.fromArray.ofMath.signMath.trunc
  7. 新增一些语法,例如扩展操作符、解构、函数默许参数等
  8. 新增一种根本数据类型Symbol
  9. 新增元编程相关,例如proxyReflect
  10. SetMap数据结构
  11. Promise
  12. Generator生成器

45.weakmap、weakset(美团19年)

参考答案:

WeakSet方针是一些方针值的调集, 而且其间的每个方针值都只能出现一次。在WeakSet的调集中是仅有的

它和Set方针的差异有两点:

  • Set比较,WeakSet只能是方针的调集,而不能是任何类型的恣意值。
  • WeakSet持弱引证:调集中方针的引证为弱引证。 假设没有其他的对WeakSet中方针的引证,那么这些方针会被当成废物收回掉。 这也意味着WeakSet中没有存储当时方针的列表。 正因为这样,WeakSet是不行枚举的。

WeakMap方针也是键值对的调集。它的键有必要是方针类型,值能够是恣意类型。它的键被弱坚持,也便是说,当其键所指方针没有其他当地引证的时分,它会被GC收回掉。WeakMap供给的接口与Map相同。

Map方针不同的是,WeakMap的键是不行枚举的。不供给列出其键的办法。列表是否存在取决于废物收回器的状况,是不行预知的。

46. 为什么ES6会新增Promise(美团 19年)

参考答案:

ES6曾经,处理异步的办法是回调函数。可是回调函数有一个最大的问题便是回调阴间(callback hell),当咱们的回调函数嵌套的层数过多时,就会导致代码横向开展。

Promise的出现便是为了处理回调阴间的问题。

47.ES5完结承继?(虾皮)

参考答案:

  1. 借用结构函数完结承继
function Parent1(){
    this.name = "parent1"
}
function Child1(){
    Parent1.call(this);
    this.type = "child1";
}

缺陷:Child1无法承继Parent1的原型方针,并没有真实的完结承继 (部分承继)。

  1. 借用原型链完结承继
function Parent2(){
    this.name = "parent2";
    this.play = [1,2,3];
}
function Child2(){
    this.type = "child2";
}
Child2.prototype = new Parent2();

缺陷:原型方针的特色是同享的。

  1. 组合式承继
function Parent3(){
    this.name = "parent3";
    this.play = [1,2,3];
}
function Child3(){
    Parent3.call(this);
    this.type = "child3";
}
Child3.prototype = Object.create(Parent3.prototype);
Child3.prototype.constructor = Child3;

48. 科里化?(搜狗)

参考答案:

柯里化,英语全称Currying,是把承受多个参数的函数变换成承受一个单一参数(开始函数的第一个参数)的函数,而且回来承受余下的参数而且回来成果的新函数的技能。

举个比方,便是把原本:

function(arg1,arg2) 变成function(arg1)(arg2)
function(arg1,arg2,arg3) 变成function(arg1)(arg2)(arg3)
function(arg1,arg2,arg3,arg4) 变成function(arg1)(arg2)(arg3)(arg4)

总而言之,便是将:

function(arg1,arg2,…,argn) 变成function(arg1)(arg2)…(argn)

49. 防抖和节省?(虾皮)

参考答案:

咱们在平常开发的时分,会有许多场景会频繁触发事情,比方说查找框实时发恳求,onmousemove、resize、onscroll等,有些时分,咱们并不能或许不想频繁触发事情,这时分就应该用到函数防抖和函数节省。

函数防抖(debounce),指的是短时刻内多次触发同一事情,只履行最终一次,或许只履行最开始的一次,中间的不履行。

函数节省(throttle),指连续触发事情可是在n秒中只履行一次函数。即2n秒内履行2次… 。节省如字面意思,会稀释函数的履行频率。

50. 闭包?(好未来—探讨了40分钟)

参考答案:

请参看前面第20题以及第36题答案。

51. 原型和原型链?(字节)

参考答案:

请参看前面第34题答案。

52. 排序算法—(时刻杂乱度、空间杂乱度)

参考答案:

算法(Algorithm)是指用来操作数据、处理程序问题的一组办法。关于同一个问题,运用不同的算法,也许最终得到的成果是相同的,但在进程中消耗的资源和时刻却会有很大的差异。

首要仍是从算法所占用的「时刻」和「空间」两个维度去考量。

  • 时刻维度:是指履行当时算法所消耗的时刻,咱们通常用「时刻杂乱度」来描绘。
  • 空间维度:是指履行当时算法需求占用多少内存空间,咱们通常用「空间杂乱度」来描绘。

因而,点评一个算法的功率首要是看它的时刻杂乱度和空间杂乱度状况。然而,有的时分时刻和空间却又是「鱼和熊掌」,不行兼得的,那么咱们就需求从中去取一个平衡点。

排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的次序进行排列的进程

排序的分类分为内部排序外部排序法

  • 内部排序:指将需求处理的一切数据都加载到**内部存储器(内存)**中进行排序。
  • 外部排序:数据量过大,无法悉数加载到内存中,需求凭借**外部存储(文件等)**进行排序。

2022高频前端面试题合集之JavaScript篇(上)

53. 浏览器事情循环和node事情循环(搜狗)

参考答案:

  1. 浏览器中的Event Loop

事情循环中的异步行列有两种:macro(宏使命)行列和micro(微使命)行列。宏使命行列能够有多个,微使命行列只需一个

  • 常见的macro-task比方:setTimeout、setInterval、 setImmediate、script(全体代码)、I/O操作、UI烘托等。
  • 常见的micro-task比方:process.nextTick、new Promise( ).then(回调)、MutationObserver(html5新特性) 等。

当某个宏使命履行完后,会检查是否有微使命行列。假设有,先履行微使命行列中的一切使命,假设没有,会读取宏使命行列中排在最前的使命,履行宏使命的进程中,遇到微使命,顺次参加微使命行列。栈空后,再次读取微使命行列里的使命,顺次类推。

  1. Node中的事情循环

Node中的Event Loop和浏览器中的是彻底不相同的东西。Node.js选用V8作为js的解析引擎,而I/O处理方面运用了自己设计的libuvlibuv是一个依据事情驱动的跨渠道抽象层,封装了不同操作系统一些底层特性,对外供给统一的API,事情循环机制也是它里边的完结。

Node.JS的事情循环分为6个阶段:

  • timers阶段:这个阶段履行timersetTimeout、setInterval)的回调
  • I/O callbacks阶段:处理一些上一轮循环中的少量未履行的I/O回调
  • idle、prepare阶段:仅Node.js内部运用
  • poll阶段:获取新的I/O事情, 恰当的条件下Node.js将堵塞在这儿
  • check阶段:履行setImmediate( ) 的回调
  • close callbacks阶段:履行socketclose事情回调

Node.js的运转机制如下:

  • V8引擎解析JavaScript脚本。
  • 解析后的代码,调用Node API
  • libuv库担任Node API的履行。它将不同的使命分配给不同的线程,构成一个Event Loop(事情循环),以异步的办法将使命的履行成果回来给V8引擎。
  • V8引擎再将成果回来给用户。

54. 闭包的优点

参考答案:

请参看前面第20题以及第36题答案。

55.let、const、var的差异

参考答案:

  1. var界说的变量,没有块的概念,能够跨块拜访, 不能跨函数拜访,有变量提高。
  2. let界说的变量,只能在块效果域里拜访,不能跨块拜访,也不能跨函数拜访,无变量提高,不行以重复声明。
  3. const用来界说常量,运用时有必要初始化(即有必要赋值),只能在块效果域里拜访,而且不能修正,无变量提高,不行以重复声明。

56. 闭包、效果域(能够扩充到效果域链)

参考答案:

什么是作业域?

ES5 中只存在两种效果域:大局效果域和函数效果域。在 JavaScript 中,咱们将效果域界说为一套规矩,这套规矩用来办理引擎怎样在当时效果域以及嵌套子效果域中依据标识符名称进行变量(变量名或许函数名)查找。

什么是效果域链?

当拜访一个变量时,编译器在履行这段代码时,会首要从当时的效果域中查找是否有这个标识符,假设没有找到,就会去父效果域查找,假设父效果域还没找到持续向上查找,直到大局效果域停止,,而效果域链,便是有当时效果域与上层效果域的一系列变量方针组成,它确保了当时履行的效果域对契合拜访权限的变量和函数的有序拜访。

闭包产生的实质

当时环境中存在指向父级效果域的引证

什么是闭包

闭包是一种特别的方针,它由两部分组成:履行上下文(代号 A),以及在该履行上下文中创立的函数 (代号 B),当 B 履行时,假设拜访了 A 中变量方针的值,那么闭包就会产生,且在 Chrome 中运用这个履行上下文 A 的函数名代指闭包。

一般怎样产生闭包

  • 回来函数
  • 函数作为参数传递

闭包的运用场景

  • 柯里化 bind
  • 模块

57.Promise

参考答案:

Promise是异步编程的一种处理方案,比传统的处理方案——回调函数和事情——更合理且更强壮。它最早由社区提出并完结,ES6将其写进了言语标准,统一了用法,并原生供给了Promise方针。

特色

  1. 方针的状况不受外界影响 (3种状况)

    • Pending状况(进行中)
    • Fulfilled状况(已成功)
    • Rejected状况(已失利)
  2. 一旦状况改动就不会再变 (两种状况改动:成功或失利)

    • Pending->Fulfilled
    • Pending->Rejected

用法

var promise = new Promise(function(resolve, reject){
    // ... some code
    if (/* 异步操作成功 */) {
        resolve(value);
    } else {
        reject(error);
    }
})

58. 完结一个函数,对一个url进行恳求,失利就再次恳求,超越最大次数就走失利回调,任何一次成功都走成功回调

参考答案:

示例代码如下:

/**
    @params url: 恳求接口地址;
    @params body: 设置的恳求体;
    @params succ: 恳求成功后的回调
    @params error: 恳求失利后的回调
    @params maxCount: 设置恳求的数量
*/
function request(url, body, succ, error, maxCount = 5) {
    return fetch(url, body)
        .then(res => succ(res))
        .catch(err => {
            if (maxCount <= 0) return error('恳求超时');
            return request(url, body, succ, error, --maxCount);
        });
}
// 调用恳求函数
request('https://java.some.com/pc/reqCount', { method: 'GET', headers: {} },
    (res) => {
        console.log(res.data);
    },
    (err) => {
        console.log(err);
    })

59. 冒泡排序

参考答案:

冒泡排序的核心思维是:

  1. 比较相邻的两个元素,假设前一个比后一个大或许小(取决于排序的次序是小到大仍是大到小),则交换方位。
  2. 比较完第一轮的时分,最终一个元素是最大或最小的元素。
  3. 这时分最终一个元素现已是最大或最小的了,所以下一次冒泡的时分最终一个元素不需求参加比较。

示例代码:

function bSort(arr) {
    var len = arr.length;
    // 外层 for 循环操控冒泡的次数
    for (var i = 0; i < len - 1; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            // 内层 for 循环操控每一次冒泡需求比较的次数
            // 因为之后每一次冒泡的两两比较次数会越来越少,所以 -i
            if (arr[j] > arr[j + 1]) {
                var temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    return arr;
}
//举个数组
myArr = [20, -1, 27, -7, 35];
//运用函数
console.log(bSort(myArr)); // [ -7, -1, 20, 27, 35 ]

60. 数组降维

参考答案:

数组降维便是将一个嵌套多层的数组进行降维操作,也便是对数组进行扁平化。在ES5年代咱们需求自己手写办法或许凭借函数库来完结,可是现在能够运用ES6新供给的数组办法flat来完结数组降维操作。

解析:运用flat办法会接纳一个参数,这个参数是数值类型,是要处理扁平化数组的深度,生成后的新数组是独立存在的,不会对原数组产生影响。

flat办法的语法如下:

var newArray = arr.flat([depth])

其间depth指定要提取嵌套数组结构的深度,默许值为1

示例如下:

var arr = [1, 2, [3, 4, [5, 6]]];
console.log(arr.flat());      // [1, 2, 3, 4, [5, 6]]
console.log(arr.flat(2));     // [1, 2, 3, 4, 5, 6]

上面的代码界说了一个层嵌套的数组,默许状况下只会拍平一层数组,也便是把本来的三维数组下降到了二维数组。在传入的参数为2时,则会下降两维,成为一个一维数组。

运用Infinity,可展开恣意深度的嵌套数组,示例如下:

var arr = [1, 2, [3, 4, [5, 6, [7, 8]]]];
console.log(arr.flat(Infinity));  // [1, 2, 3, 4, 5, 6, 7, 8]

在数组中有空项的时分,运用flat办法会将中的空项进行移除。

var arr = [1, 2, , 4, 5];
console.log(arr.flat()); // [1, 2, 4, 5]

上面的代码中,数组中第三项是空值,在运用flat后会对空项进行移除。

61.call apply bind

参考答案:

请参看前面第11题答案。

62. promise 代码题

new Promise((resolve, reject) => {
    reject(1);
    console.log(2);
    resolve(3);
    console.log(4);
}).then((res) => { console.log(res) })
    .catch(res => { console.log('reject1') })
try {
    new Promise((resolve, reject) => {
        throw 'error'
    }).then((res) => { console.log(res) })
        .catch(res => { console.log('reject2') })
} catch (err) {
    console.log(err)
}

参考答案:

2
4
reject1
reject2

直播课或许录播课进行解析。

63.proxy是完结署理,能够改动js底层的完结办法, 然后说了一下和Object.defineProperty的差异

参考答案:

两者的差异总结如下:

  • 署理原理:Object.defineProperty的原理是经过将数据特色转变为存取器特色的办法完结的特色读写署理。而Proxy则是因为这个内置的Proxy方针内部有一套监听机制,在传入handler方针作为参数结构署理方针后,一旦署理方针的某个操作触发,就会进入handler中对应注册的处理函数,此刻咱们就能够有选择的运用Reflect将操作转发被署理方针上。
  • 署理局限性:Object.defineProperty一向仍是局限于特色层面的读写署理,关于方针层面以及特色的其它操作署理它都无法完结。鉴于此,因为数组方针push、pop等办法的存在,它关于数组元素的读写署理完结的并不彻底。而运用Proxy则能够很便利的监督数组操作。
  • 自我署理:Object.defineProperty办法能够署理到本身(署理之后运用方针本身即可),也能够署理到其他方针身上(署理之后需求运用署理方针)。Proxy办法只能署理到Proxy实例方针上。这一点在其它说法中是Proxy方针不需求侵入方针就能够完结署理,实践上Object.defineProperty办法也能够不侵入。

64. 运用ES5ES6别离完结承继

参考答案:

假设是运用ES5来完结承继,那么现在的最优解是运用圣杯办法。圣杯办法的核心思维便是不经过调用父类结构函数来给子类原型赋值,而是获得父类原型的一个副本,然后将回来的新方针赋值给子类原型。详细代码能够参看前面第9题的解析。

ES6新增了extends要害字,直接运用该要害字就能够完结承继。

65. 深仿制

参考答案:

有深仿制就有浅仿制。

浅仿制便是只仿制方针的引证,而不深层次的仿制方针的值,多个方针指向堆内存中的同一方针,任何一个修正都会使得一切方针的值修正,因为它们共用一条数据。

深仿制不是单纯的仿制一份引证数据类型的引证地址,而是将引证类型的值悉数仿制一份,构成一个新的引证类型,这样就不会产生引证错乱的问题,使得咱们能够多次运用相同的数据,而不必忧虑数据之间会起抵触。

解析:

「深仿制」便是在仿制数据的时分,将数据的一切引证结构都仿制一份。简略的说便是,在内存中存在两个数据结构彻底相同又彼此独立的数据,将引证型类型进行仿制,而不是只仿制其引证联络。

分析下怎样做「深仿制」:

  1. 首要假设深仿制这个办法现已完结,为 deepClone
  2. 要仿制一个数据,咱们肯定要去遍历它的特色,假设这个方针的特色仍是方针,持续运用这个办法,如此往复
function deepClone(o1, o2) {
    for (let k in o2) {
        if (typeof o2[k] === 'object') {
            o1[k] = {};
            deepClone(o1[k], o2[k]);
        } else {
            o1[k] = o2[k];
        }
    }
}
// 测验用例
let obj = {
    a: 1,
    b: [1, 2, 3],
    c: {}
};
let emptyObj = Object.create(null);
deepClone(emptyObj, obj);
console.log(emptyObj.a == obj.a);
console.log(emptyObj.b == obj.b);

递归简略形成爆栈,尾部调用能够处理递归的这个问题,ChromeV8引擎做了尾部调用优化,咱们在写代码的时分也要留意尾部调用写法。递归的爆栈问题能够经过将递归改写成枚举的办法来处理,便是经过for或许while来替代递归。

66.asyncawait的效果

参考答案:

async是一个修饰符,async界说的函数会默许的回来一个Promise方针resolve的值,因而对async函数能够直接进行then操作,回来的值即为then办法的传入函数。

await要害字只能放在async函数内部,await要害字的效果便是获取Promise中回来的内容, 获取的是Promise函数中resolve或许reject的值。

67. 数据的基础类型(原始类型)有哪些

参考答案:

JavaScript中的基础数据类型,一共有6种:

string,symbol,number,boolean,undefined,null

其间symbol类型是在ES6里边新增加的根本数据类型。

68.typeof null回来成果

参考答案:

回来object

解析:至于为什么会回来object,这实践上是来源于JavaScript从第一个版别开始时的一个bug,而且这个bug无法被修正。修正会损坏现有的代码。

原理这是这样的,不同的方针在底层都表现为二进制,在JavaScript中二进制前三位都为0的话会被判别为object类型,null的二进制悉数为0,天然前三位也是0,所以履行typeof值会回来object

69. 对变量进行类型判其他办法有哪些

参考答案:

常用的办法有4种:

  1. typeof

typeof是一个操作符,其右侧跟一个一元表达式,并回来这个表达式的数据类型。回来的成果用该类型的字符串(全小写字母)办法表明,包括以下7种:number、boolean、symbol、string、object、undefined、function等。

  1. instanceof

instanceof是用来判别A是否为B的实例,表达式为:A instanceof B,假设AB的实例,则回来true,不然回来false。 在这儿需求特别留意的是:instanceof检测的是原型。

  1. constructor

当一个函数F被界说时,JS引擎会为F增加prototype原型,然后再在prototype上增加一个constructor特色,并让其指向F的引证。

  1. toString

toString( )Object的原型办法,调用该办法,默许回来当时方针的Class。这是一个内部特色,其格式为 [object Xxx] ,其间Xxx便是方针的类型。

关于Object方针,直接调用toString( ) 就能回来 [object Object] 。而关于其他方针,则需求经过call / apply来调用才干回来正确的类型信息。例如:

Object.prototype.toString.call('') ;  // [object String]
Object.prototype.toString.call(1) ;   // [object Number]
Object.prototype.toString.call(true) ;// [object Boolean]
Object.prototype.toString.call(Symbol());//[object Symbol]
Object.prototype.toString.call(undefined) ;// [object Undefined]
Object.prototype.toString.call(null) ;// [object Null]

70.typeofinstanceof的差异?instanceof是怎样完结?

参考答案:

  1. typeof

typeof是一个操作符,其右侧跟一个一元表达式,并回来这个表达式的数据类型。回来的成果用该类型的字符串(全小写字母)办法表明,包括以下7种:number、boolean、symbol、string、object、undefined、function等。

  1. instanceof

instanceof是用来判别A是否为B的实例,表达式为:A instanceof B,假设AB的实例,则回来true,不然回来false。 在这儿需求特别留意的是:instanceof检测的是原型。

用一段伪代码来模拟其内部履行进程:

instanceof (A,B) = {
    varL = A.__proto__;
    varR = B.prototype;
    if(L === R) {
        // A的内部特色 __proto__ 指向 B 的原型方针
        return true;
    }
    return false;
}

从上述进程能够看出,当Aproto 指向Bprototype时,就认为A便是B的实例。

需求留意的是,instanceof只能用来判别两个方针是否归于实例联络, 而不能判别一个方针实例详细归于哪种类型。

例如: [ ] instanceof Object回来的也会是true

71. 引证类型有哪些,有什么特色

参考答案:

JS 中七种内置类型(null,undefined,boolean,number,string,symbol,object)又分为两大类型

两大类型:

  • 根本类型:nullundefinedbooleannumberstringsymbol
  • 引证类型Object:ArrayFunctionDateRegExp

2022高频前端面试题合集之JavaScript篇(上)

根本类型和引证类型的首要差异有以下几点:

存放方位:

  • 根本数据类型:根本类型值在内存中占有固定巨细,直接存储在栈内存中的数据
  • 引证数据类型:引证类型在栈中存储了指针,这个指针指向堆内存中的地址,真实的数据存放在堆内存里。

2022高频前端面试题合集之JavaScript篇(上)

值的可变性:

  • 根本数据类型: 值不行变,javascript中的原始值(undefined、null、布尔值、数字和字符串)是不行更改的
  • 引证数据类型:引证类型是能够直接改动其值的

比较:

  • 根本数据类型: 根本类型的比较是值的比较,只需它们的值持平就认为他们是持平的
  • 引证数据类型: 引证数据类型的比较是引证的比较,看其的引证是否指向同一个方针

72. 怎样得到一个变量的类型—指函数封装完结

参考答案:

请参看前面第30题答案。

73. 什么是效果域、闭包

参考答案:

请参看前面第56题。

74. 闭包的缺陷是什么?闭包的运用场景有哪些?怎样毁掉闭包?

参考答案:

闭包是指有权拜访其他一个函数效果域中的变量的函数。

因为闭包引证着另一个函数的变量,导致另一个函数现已不运用了也无法毁掉,所以闭包运用过多,会占用较多的内存,这也是一个副效果,内存走漏。

假设要毁掉一个闭包,能够 把被引证的变量设置为null,即手动铲除变量,这样下次js废物收回机制收回时,就会把设为null的量给收回了。

闭包的运用场景:

  1. 匿名自履行函数
  2. 成果缓存
  3. 封装
  4. 完结类和承继

75.JS的废物收回站机制

参考答案:

JS具有主动废物收回机制。废物收集器会依照固定的时刻距离周期性的履行。

JS常见的废物收回办法:符号铲除、引证计数办法。

1、符号铲除办法:

  • 作业原理:当变量进入环境时,将这个变量符号为“进入环境”。当变量脱离环境时,则将其符号为“脱离环境”。符号“脱离环境”的就收回内存。
  • 作业流程:
  • 废物收回器,在运转的时分会给存储在内存中的一切变量都加上符号;
  • 去掉环境中的变量以及被环境中的变量引证的变量的符号;
  • 被加上符号的会被视为准备删去的变量;
  • 废物收回器完结内存清理作业,毁掉那些带符号的值并收回他们所占用的内存空间。

2、引证计数办法:

  • 作业原理:跟踪记载每个值被引证的次数。
  • 作业流程:
  • 声明晰一个变量并将一个引证类型的值赋值给这个变量,这个引证类型值的引证次数便是1
  • 同一个值又被赋值给另一个变量,这个引证类型值的引证次数加1;
  • 当包括这个引证类型值的变量又被赋值成另一个值了,那么这个引证类型值的引证次数减1
  • 当引证次数变成0时,说明没办法拜访这个值了;
  • 当废物收集器下一次运转时,它就会开释引证次数是0的值所占的内存。

76. 什么是效果域链、原型链

参考答案:

什么是效果域链?

当拜访一个变量时,编译器在履行这段代码时,会首要从当时的效果域中查找是否有这个标识符,假设没有找到,就会去父效果域查找,假设父效果域还没找到持续向上查找,直到大局效果域停止,,而效果域链,便是有当时效果域与上层效果域的一系列变量方针组成,它确保了当时履行的效果域对契合拜访权限的变量和函数的有序拜访。

什么原型链?

每个方针都能够有一个原型__proto__,这个原型还能够有它自己的原型,以此类推,构成一个原型链。查找特定特色的时分,咱们先去这个方针里去找,假设没有的话就去它的原型方针里边去,假设仍是没有的话再去向原型方针的原型方针里去寻找。这个操作被托付在整个原型链上,这个便是咱们说的原型链。

77.new一个结构函数产生了什么

参考答案:

new运算符创立一个用户界说的方针类型的实例或具有结构函数的内置方针的实例。

new要害字会进行如下的操作:
进程1:创立一个空的简略JavaScript方针,即 { } ;
进程2:链接该方针到另一个方针(即设置该方针的原型方针);
进程3:将进程1新创立的方针作为this的上下文;
进程4:假设该函数没有回来方针,则回来this

78. 对一个结构函数实例化后. 它的原型链指向什么

参考答案:

指向该结构函数实例化出来方针的原型方针。

关于结构函数来讲,能够经过prototype拜访到该方针。

关于实例方针来讲,能够经过隐式特色 proto 来拜访到。

79. 什么是变量提高

参考答案:

JavaScript编译一切代码时,一切运用var的变量声明都被提高到它们的函数/局部效果域的顶部(假设在函数内部声明的话),或许提高到它们的大局效果域的顶部(假设在函数外部声明的话),而不管实践的声明是在哪里进行的。这便是咱们所说的“提高”。

请记住,这种“提高”实践上并不产生在你的代码中,而仅仅一种比方,与JavaScript编译器怎样读取你的代码有关。记住当咱们想到“提高”的时分,咱们能够幻想任何被提高的东西都会被移动到顶部,可是实践上你的代码并不会被修正。

函数声明也会被提高,可是被提高到了最顶端,所以将位于一切变量声明之上。

在编译阶段变量和函数声明会被放入内存中,可是你在代码中编写它们的方位会坚持不变。

80. == 和 === 的差异是什么

参考答案:

简略来说: == 代表相同, === 代表严厉相同(数据类型和值都持平)。

当进行双等号比较时分,先检查两个操作数数据类型,假设相同,则进行===比较,假设不同,则愿意为你进行一次类型转化,转化成相同类型后再进行比较,而 === 比较时,假设类型不同,直接便是false。

从这个进程来看,咱们也能发现,某些状况下咱们运用 === 进行比较功率要高些,因而,没有歧义的状况下,不会影响成果的状况下,在JS中首选 === 进行逻辑比较。

81.Object.is办法比较的是什么

参考答案:

Object.is办法是ES6新增的用来比较两个值是否严厉持平的办法,与 === (严厉持平)的行为根本共同。不过有两处不同:

  • +0 不等于 -0。
  • NaN等于本身。

所以能够将*Object.is*办法看作是加强版的严厉持平。

82. 基础数据类型和引证数据类型,哪个是保存在栈内存中?哪个是在堆内存中?

参考答案:

ECMAScript标准中,共界说了7种数据类型,分为根本类型引证类型两大类,如下所示:

  • 根本类型String、Number、Boolean、Symbol、Undefined、Null
  • 引证类型Object

根本类型也称为简略类型,因为其占有空间固定,是简略的数据段,为了便于提高变量查询速度,将其存储在栈中,即按值拜访。

引证类型也称为杂乱类型,因为其值的巨细会改动,所以不能将其存放在栈中,不然会下降变量查询速度,因而,其值存储在堆(heap)中,而存储在变量处的值,是一个指针,指向存储方针的内存处,即按址拜访。引证类型除Object外,还包括Function 、Array、RegExp、Date等等。

83. 箭头函数处理了什么问题?

参考答案:

箭头函数首要处理了this的指向问题。

解析:

ES5年代,一旦方针的办法里边又存在函数,则this的指向往往会让开发人员抓狂。

例如:

//过错事例,this 指向会指向 Windows 或许 undefined
var obj = {
    age: 18,
    getAge: function () {
        var a = this.age; // 18
        var fn = function () {
            return new Date().getFullYear() - this.age; // this 指向 window 或 undefined
        };
        return fn();
    }
};
console.log(obj.getAge()); // NaN

然而,箭头函数没有this,箭头函数的this是承继父履行上下文里边的this

var obj = {
    age: 18,
    getAge: function () {
        var a = this.age; // 18
        var fn = () => new Date().getFullYear() - this.age; // this 指向 obj 方针
        return fn();
    }
};
console.log(obj.getAge()); // 2003

84.new一个箭头函数后,它的this指向什么?

参考答案:

我不知道这道题是出题人写错了仍是成心为之。

箭头函数无法用来充任结构函数,所以是无法new一个箭头函数的。

当然,也有或许是面试官成心挖的一个坑,等着你往里边跳。

85.promise的其他办法有用过吗?如all、race。请说下这两者的差异

参考答案:

promise.all办法参数是一个promise的数组,只需当一切的promise都完结并回来成功,才会调用resolve,当有一个失利,都会进catch,被捕获过错,promise.all调用成功回来的成果是每个promise独自调用成功之后回来的成果组成的数组,假设调用失利的话,回来的则是第一个reject的成果

promise.race也会调用一切的promise,回来的成果则是一切promise中最先回来的成果,不关心是成功仍是失利。

86.class是怎样完结的

参考答案:

classES6新推出的要害字,它是一个语法糖,实质上便是依据这个原型完结的。只不过在曾经ES5原型完结的基础上,增加了一些 _classCallCheck、_defineProperties、_createClass等办法来做出了一些特别的处理。

例如:

class Hello {
   constructor(x) {
       this.x = x;
   }
   greet() {
       console.log("Hello, " + this.x)
   }
}
"use strict";
function _classCallCheck(instance, Constructor) {
     if (!(instance instanceof Constructor)) {
         throw new TypeError("Cannot call a class as a function");
     }
}
function _defineProperties(target, props) {
     for (var i = 0; i < props.length; i++) {
         var descriptor = props[i];
         descriptor.enumerable = descriptor.enumerable || false;
         descriptor.configurable = true;
         if ("value" in descriptor)
             descriptor.writable = true;
         Object.defineProperty(target, descriptor.key, descriptor);
     }
}
function _createClass(Constructor, protoProps, staticProps) {
     console.log("Constructor::",Constructor);
     console.log("protoProps::",protoProps);
     console.log("staticProps::",staticProps);
     if (protoProps)
         _defineProperties(Constructor.prototype, protoProps);
     if (staticProps)
         _defineProperties(Constructor, staticProps);
     return Constructor;
}
var Hello = /*#__PURE__*/function () {
   function Hello(x) {
       _classCallCheck(this, Hello);
       this.x = x;
   }
   _createClass(Hello, [{
       key: "greet",
       value: function greet() {
         console.log("Hello, " + this.x);
       }
 	}]);
 	return Hello;
}();

87.let、const、var的差异

参考答案:

请参看前面第22题答案。

88.ES6中模块化导入和导出与common.js有什么差异

参考答案:

CommonJs模块输出的是值的仿制,也便是说,一旦输出一个值,模块内部的变化不会影响到这个值.

// common.js
var count = 1;
var printCount = () =>{ 
  return ++count;
}
module.exports = {
    printCount: printCount,
    count: count
};
// index.js
let v = require('./common');
console.log(v.count); // 1
console.log(v.printCount()); // 2
console.log(v.count); // 1

你能够看到明明common.js里边改动了count,可是输出的成果仍是本来的。这是因为count是一个原始类型的值,会被缓存。除非写成一个函数,才干得到内部变化的值。将common.js里边的module.exports 改写成

module.exports = {
    printCount: printCount,
    get count(){
        return count
    }
};

这姿态的输出成果是 1,2,2

而在ES6当中,写法是这样的,是运用export 和import导入的

// es6.js
export let count = 1;
export function printCount() {
    ++count;
}
// main1.js
import  { count, printCount } from './es6';
console.log(count)
console.log(printCount());
console.log(count)

ES6 模块是动态引证,而且不会缓存,模块里边的变量绑定其一切的模块,而是动态地去加载值,而且不能从头赋值,

ES6 输入的模块变量,仅仅一个“符号连接符”,所以这个变量是只读的,对它进行从头赋值会报错。假设是引证类型,变量指向的地址是只读的,可是能够为其增加特色或成员。

其他还想说一个export default

let count = 1;
function printCount() {
    ++count;
} 
export default { count, printCount}
// main3.js
import res form './main3.js'
console.log(res.count)

export与export default的差异及联络:

  1. export与export default均可用于导出常量、函数、文件、模块等
  2. 你能够在其它文件或模块中经过 import + (常量 | 函数 | 文件 | 模块)名的办法,将其导入,以便能够对其进行运用
  3. 在一个文件或模块中,export、import能够有多个,export default仅有一个
  4. 经过export办法导出,在导入时要加{ },export default则不需求。

89. 说一下一般函数和箭头函数的差异

参考答案:

请参看前面第8、25、83题答案。

90. 说一下promiseasyncawait什么联络

参考答案:

await表达式会形成异步函数停止履行而且等候promise的处理,当值被resolved,异步函数会康复履行以及回来resolved值。假设该值不是一个promise,它将会被转化成一个resolved后的promise。假设promiserejectedawait表达式会抛出异常值。

91. 说一下你学习过的有关ES6的常识点

参考答案:

这种标题是敞开题,能够简略列举一下ES6的新增常识点。(ES6的新增常识点参看前面第44题)

然后说一下自己平常开发中用得比较多的是哪些即可。

一般面试官会针对你所说的内容进行二次发问。例如:你答复平常开发中箭头函数用得比较多,那么面试官极大或许针对箭头函数展开二次发问,问询你箭头函数有哪些特性?箭头函数this特色之类的问题。

92. 了解过jsarguments吗?接纳的是实参仍是形参?

参考答案:

JS中的arguments是一个伪数组方针。这个伪数组方针将包括调用函数时传递的一切的实参。

与之相对的,JS中的函数还有一个length特色,回来的是函数形参的个数。

93.ES6比较于ES5有什么变化

参考答案:

ES6比较ES5新增了许多新特性,这儿能够自己简述几个。

详细的新增特功用够参看前面第44题。

94. 强制类型转化办法有哪些?

参考答案:

JavaScript 中的数据类型转化,首要有三种办法:

  1. 转化函数

js供给了诸如parseIntparseFloat这些转化函数,经过这些转化函数能够进行数据类型的转化 。

  1. 强制类型转化

还可运用强制类型转化(type casting)处理转化值的类型。

例如:

  • Boolean(value) 把给定的值转化成Boolean型;

  • Number(value)——把给定的值转化成数字(能够是整数或浮点数);

  • String(value)——把给定的值转化成字符串。

  1. 运用js变量弱类型转化。

例如:

  • 转化字符串:直接和一个空字符串拼接,例如:a = "" + 数据
  • 转化布尔:!!数据类型,例如:!!"Hello"
  • 转化数值:数据*1 或 /1,例如:"Hello * 1"

95. 纯函数

参考答案:

一个函数,假设契合以下两个特色,那么它就能够称之为纯函数

  1. 关于相同的输入,永久得到相同的输出
  2. 没有任何可观察到的副效果

解析:

针对上面的两个特色,咱们一个一个来看。

  • 相同输入得到相同输出

咱们先来看一个不纯的反面典型:

let greeting = 'Hello'
function greet (name) {
  return greeting + ' ' + name
}
console.log(greet('World')) // Hello World

上面的代码中,greet(‘World’) 是不是永久回来Hello World? 显然不是,假设咱们修正greeting的值,就会影响greet函数的输出。即函数greet其实是依赖外部状况的。

那咱们做以下修正:

function greet (greeting, name) {
  return greeting + ' ' + name
}
console.log(greet('Hi', 'Savo')) // Hi Savo

greeting参数也传入,这样关于任何输入参数,都有与之对应的仅有的输出参数了,该函数就契合了第一个特色。

  • 没有副效果

副效果的意思是,这个函数的运转,不会修正外部的状况

下面再看反面典型:

const user = {
  username: 'savokiss'
}
let isValid = false
function validate (user) {
  if (user.username.length > 4) {
    isValid = true
  }
}

可见,履行函数的时分会修正到isValid的值(留意:假设你的函数没有任何回来值,那么它很或许就具有副效果!)

那么咱们怎样移除这个副效果呢?其实不需求修正外部的isValid变量,咱们只需求在函数中将验证的成果return出来:

const user = {
  username: 'savokiss'
}
function validate (user) {
  return user.username.length > 4;
}
const isValid = validate(user)

这样validate函数就不会修正任何外部的状况了~

96.JS模块化

参考答案:

模块化首要是用来抽离公共代码,阻隔效果域,防止变量抵触等。

模块化的整个开展前史如下:

IIFE: 运用自履行函数来编写模块化,特色:在一个独自的函数效果域中履行代码,防止变量抵触

(function(){
 return {
	data:[]
 }
})()

AMD: 运用requireJS 来编写模块化,特色:依赖有必要提前声明好

define('./index.js',function(code){
	// code 便是index.js 回来的内容
})

CMD: 运用seaJS 来编写模块化,特色:支撑动态引进依赖文件

define(function(require, exports, module) {
 var indexCode = require('./index.js');
});

CommonJS: nodejs 中自带的模块化。

var fs = require('fs');

UMD:兼容AMD,CommonJS 模块化语法。

webpack(require.ensure) :webpack 2.x 版别中的代码分割。

ES Modules: ES6 引进的模块化,支撑import 来引进另一个 js 。

import a from 'a';

97. 看过jquery源码吗?

参考答案:

敞开题,可是需求留意的是,假设看过jquery源码,不要简略的答复一个“看过”就完了,应该持续乘胜追击,告知面试官例如哪个哪个部分是怎样怎样完结的,并针对这部分的源码完结,能够宣布一些自己的观点和感想。

98. 说一下js中的this

参考答案:

请参看前面第17题答案。

99.apply call bind差异,手写

参考答案:

apply call bind 差异 ?

callapply的功用相同,差异在于传参的办法不相同:

  • fn.call(obj, arg1, arg2, …) 调用一个函数, 具有一个指定的this值和别离地供给的参数(参数的列表)。
  • fn.apply(obj, [argsArray]) 调用一个函数,具有一个指定的this值,以及作为一个数组(或类数组方针)供给的参数。

bindcall/apply有一个很重要的差异,一个函数被call/apply的时分,会直接调用,可是bind会创立一个新函数。当这个新函数被调用时,bind( ) 的第一个参数将作为它运转时的this,之后的一序列参数将会在传递的实参前传入作为它的参数。

完结call办法:

Function.prototype.call2 = function (context) {
   //没传参数或许为 null 是默许是 window
   var context = context || (typeof window !== 'undefined' ? window : global)
   // 首要要获取调用 call 的函数,用 this 能够获取
   context.fn = this
   var args = []
   for (var i = 1; i < arguments.length; i++) {
       args.push('arguments[' + i + ']')
   }
   eval('context.fn(' + args + ')')
   delete context.fn
}
// 测验
var value = 3
var foo = {
   value: 2
}
function bar(name, age) {
   console.log(this.value)
   console.log(name)
   console.log(age)
}
bar.call2(null)
// 浏览器环境: 3 undefinde undefinde   
// Node环境:undefinde undefinde undefinde
bar.call2(foo, 'cc', 18) // 2  cc 18

完结apply办法:

Function.prototype.apply2 = function (context, arr) {
   var context = context || (typeof window !== 'undefined' ? window : global)
   context.fn = this;
   var result;
   if (!arr) {
       result = context.fn();
   }
   else {
       var args = [];
       for (var i = 0, len = arr.length; i < len; i++) {
           args.push('arr[' + i + ']');
       }
       result = eval('context.fn(' + args + ')')
   }
   delete context.fn
   return result;
}
// 测验:
var value = 3
var foo = {
   value: 2
}
function bar(name, age) {
   console.log(this.value)
   console.log(name)
   console.log(age)
}
bar.apply2(null)
// 浏览器环境: 3 undefinde undefinde   
// Node环境:undefinde undefinde undefinde
bar.apply2(foo, ['cc', 18]) // 2  cc 18

完结bind办法:

Function.prototype.bind2 = function (oThis) {
   if (typeof this !== "function") {
       // closest thing possible to the ECMAScript 5 internal IsCallable function
       throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
   }
   var aArgs = Array.prototype.slice.call(arguments, 1),
       fToBind = this,
       fNOP = function () { },
       fBound = function () {
           return fToBind.apply(this instanceof fNOP && oThis
               ? this
               : oThis || window,
               aArgs.concat(Array.prototype.slice.call(arguments)));
       };
   fNOP.prototype = this.prototype;
   fBound.prototype = new fNOP();
   return fBound;
}
// 测验
var test = {
   name: "jack"
}
var demo = {
   name: "rose",
   getName: function () { return this.name; }
}
console.log(demo.getName()); // 输出 rose  这儿的 this 指向 demo
// 运用 bind 办法更改 this 指向
var another2 = demo.getName.bind2(test);
console.log(another2()); // 输出 jack  这儿 this 指向了 test 方针了

100. 手写reduce flat

参考答案:

reduce完结:

Array.prototype.my_reduce = function (callback, initialValue) {
    if (!Array.isArray(this) || !this.length || typeof callback !== 'function') {
        return []
    } else {
        // 判别是否有初始值
        let hasInitialValue = initialValue !== undefined;
        let value = hasInitialValue ? initialValue : tihs[0];
        for (let index = hasInitialValue ? 0 : 1; index < this.length; index++) {
            const element = this[index];
            value = callback(value, element, index, this)
        }
        return value
    }
}
let arr = [1, 2, 3, 4, 5]
let res = arr.my_reduce((pre, cur, i, arr) => {
    console.log(pre, cur, i, arr)
    return pre + cur
}, 10)
console.log(res)//25

flat完结:

let arr = [1, [2, 3, [4, 5, [12, 3, "zs"], 7, [8, 9, [10, 11, [1, 2, [3, 4]]]]]]];
//万能的类型检测办法
const checkType = (arr) => {
    return Object.prototype.toString.call(arr).slice(8, -1);
}
//自界说flat办法,留意:不行以运用箭头函数,运用后内部的this会指向window
Array.prototype.myFlat = function (num) {
    //判别第一层数组的类型
    let type = checkType(this);
    //创立一个新数组,用于保存拆分后的数组
    let result = [];
    //若当时方针非数组则回来undefined
    if (!Object.is(type, "Array")) {
        return;
    }
    //遍历一切子元素并判别类型,若为数组则持续递归,若不为数组则直接参加新数组
    this.forEach((item) => {
        let cellType = checkType(item);
        if (Object.is(cellType, "Array")) {
            //形参num,表明当时需求拆分多少层数组,传入Infinity则将多维直接降为一维
            num--;
            if (num < 0) {
                let newArr = result.push(item);
                return newArr;
            }
            //运用三点运算符解构,递归函数回来的数组,并参加新数组
            result.push(...item.myFlat(num));
        } else {
            result.push(item);
        }
    })
    return result;
}
console.time();
console.log(arr.flat(Infinity)); //[1, 2, 3, 4, 5, 12, 3, "zs", 7, 8, 9, 10, 11, 1, 2, 3, 4];
console.log(arr.myFlat(Infinity)); //[1, 2, 3, 4, 5, 12, 3, "zs", 7, 8, 9, 10, 11, 1, 2, 3, 4];
//自界说办法和自带的flat回来成果共同!!!!
console.timeEnd();

注: 因为字数限制,剩下内容在下篇进行总结哦。,咱们认真看哦,奥利给!