2022前端高频面试题

1,504 阅读6分钟

前端面试题整理http://www.yoloworld.site:3000/gitblog/

闭包

  • 闭包是指有权访问另一个函数作用域中变量的函数,
  • 创建闭包的最常见的方式就是在一个函数内创建另一个函数,
  • 通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部
<ul id="testUL">
  <li> index = 0</li>
  <li> index = 1</li>
  <li> index = 2</li>
  <li> index = 3</li>
</ul>
const nodes = document.getElementsByTagName("li");
for (i = 0; i < nodes.length; i += 1) {
  // nodes[i].onclick = function () {
  //   console.log(i);   //不用闭包的话,console.log每次都是4
  // }
  // 闭包写法
  // 声明一个匿名函数作为闭包的外层
  // 调用outterFunc时将i作为参数index的值传入;
  // outterFunc方法返回的function(){console.log(idnex)}作为onclick的事件处理函数
  function outterFunc (index) {
    return function() {
      console.log(index);
    }
  }
  nodes[i].onclick = outterFunc(i);
}
// 那如果使用事件冒泡就也可以实现
const ulNode = document.getElementById("testUL");
ulNode.onclick = function (e) {
  console.log(e.target.innerText)
}

原型

原型

  • 每个实例对象(object )都有一个私有属性(称之为__proto__)指向它的原型对象(prototype)。
  • 无论什么时候创建一个函数,该函数都有一个prototype(原型)属性,即原型对象
  • 默认情况下,原型对象都会自动获取一个constructor(构造函数)属性,该属性指向prototype属性所在的函数
  • prototype(原型)就是通过构造函数来创建的对象实例的原型对象, 这个对象让所有对象实例可以共享它所包含的属性和方法
  • 所有普通的[[Prototype]]链最终都会指向内置的Object.prototype

构造函数

  • 任何函数,只要通过new操作符来调用,就可以作为构造函数
  • 构造函数new一个对象实例的过程
  1. 创建一个新对象实例;
  2. 将构造函数的作用域赋给新对象实例
  3. 执行构造函数中的代码,为新对象实例添加属性
  4. 返回新对象实例
function Person() {
  this.sayHello = function (params) {
    console.log('hello');
  };
}

Person.prototype.name = 'person';
const person1 = new Person();

// Person.prototype === person1.__proto__
console.log(JSON.stringify(Person.prototype)); // {"name":"person"}
console.log(JSON.stringify(person1.__proto__)); // {"name":"person"}
console.log(JSON.stringify(person1.prototype)); // undefined
console.log(Person.prototype === person1.__proto__); // true
console.log(`getPrototypeOf:  ${Object.getPrototypeOf(person1) === Person.prototype}`); // true
console.log(`constructor:  ${Person.prototype.constructor === Person}`); // true

console.log(person1.__proto__.constructor === Person.prototype.constructor); // true
console.log(person1.__proto__.constructor === Person); // true
console.log(person1.__proto__.constructor.prototype === Person.prototype); // true

// 对象实例中有一个constructor属性,指向构造函数
console.log(JSON.stringify(person1.constructor === Person))

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

最后一张图片的代码

function Animal(type) {
  this.type = type || 'animal';

  this.getType = function getType() {

  };
}

function Dog() {
  this.name = 'dog';
}

Dog.prototype = new Animal();

const dog = new Dog();

console.log(dog);

宏任务与微任务

代码题:

function app() {
  setTimeout(() => {
    console.log("1-1");
    Promise.resolve().then(() => {
      console.log("2-1");
    });
  });
  console.log("1-2");
  Promise.resolve().then(() => {
    console.log("1-3");
    setTimeout(() => {
      console.log("3-1");
    });
  });
}
app();

结果

1-2
1-3
1-1
2-1
3-1
  1. 先执行同步
  2. 同一层级下(不理解层级,可以先不管,后面会讲),微任务永远比宏任务先执行
  3. 每个宏任务,都单独关联了一个微任务队列

宏任务包括 宏任务 微任务包括 微任务

箭头函数

es6.ruanyifeng.com/#docs/funct…

箭头函数有几个使用注意点。

(1)箭头函数没有自己的this对象。

对于普通函数来说,内部的this指向函数运行时所在的对象,但是这一点对箭头函数不成立。 它没有自己的this对象,内部的this就是定义时上层作用域中的this

// 普通函数
function foo() {
  setTimeout(function() {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// 浏览器执行 id: 21  node执行 undefined

>-----------------------------<

// 箭头函数
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42

(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

this相关知识点

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

this是在调用时绑定的

如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this的绑定对象。——你不知道的JavaScript(上卷)

  1. 由new调用?绑定到新创建的对象。
  2. 由call或者apply(或者bind)调用?绑定到指定的对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到undefined,否则绑定到全局对象。
/**
 * 优先级
 * new绑定和隐式绑定的优先级
 */

function foo(something) {
  this.a = something;
}
const obj1 = { foo };
const obj2 = {};

obj1.foo(2); // 此时foo的this指向obj1,所以foo执行时,this.a=2相当于执行了obj1.a=2
console.log(obj1.a);// 2

obj1.foo.call(obj2, 3); // 此时foo的this指向obj2,所以foo执行时,this.a=2相当于执行了obj1.a=2
console.log(obj2.a);// 3

const bar = new obj1.foo(4);
console.log(obj1.a);// 2
console.log(bar.a);// 4
new绑定
  • 构造函数new一个对象实例的过程
  1. 创建一个新对象实例;
  2. 将构造函数的作用域赋给新对象实例
  3. 执行构造函数中的代码,为新对象实例添加属性
  4. 返回新对象实例
默认绑定

在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用。

function sayHi(){
    console.log('Hello,', this.name);
}
var name = 'YvetteLau';
sayHi();
// 在调用 Hi() 时,应用了默认绑定,this 指向全局对象(非严格模式下),
// 严格模式下,this 指向 undefined,undefined 上没有 this 对象,会抛出错误。
隐式绑定

函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。典型的形式为 XXX.fun().

对象属性链中只有最后一层会影响到调用位置。 eg :person1.friend.sayHi();

显式绑定

就是通过 call,apply,bind 的方式

call 和 apply的功能相同,都是在调用函数,并修改this指向第一个参数;区别在于传参的方式不一样:

  • fn.call(obj, arg1, arg2, ...),调用一个函数, 具有一个指定的this值和分别地提供的参数(参数的列表)。
  • fn.apply(obj, [argsArray]),调用一个函数,具有一个指定的this值,以及作为一个数组(或类数组对象)提供的参数。
// node中执行
global.a = 3;

function foo() {
  console.log(this.a);
}

const obj = { a: 2 };

foo.call(obj); // 2
// 如果将第一个参数传为一个基本类型2 此时this指向Number引用类型
// 例如 Boolean,String,Number,这个将基本类型转为引用类型的操作成为“装箱”
foo.call(2);
foo.call(null); // 如果把undefined和null作为绑定对象传给call或者apply,此时应用的是默认绑定规则

如果把undefined和null作为绑定对象传给call或者apply,此时应用的是默认绑定规则;

bind

bind(..)会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。——你不知道的JavaScript(上卷)

硬绑定

应用场景:创建一个包裹函数,传入所有的参数并返回接收到的所有值;这就是ES5中bind的由来

/**
 * 硬绑定
 * 应用场景:创建一个包裹函数,传入所有的参数并返回接收到的所有值
 */

function foo() {
  console.log(`foo: ${this.a}`);
}
global.a = 3; // node
window.a = 3; // 浏览器

const obj = { a: 2 };
function bar() {
  // 强制将foo的this绑定到obj,对于bar函数的调用方式不会影响foo函数this的指向,
  // 这种显式的强制绑定,成为硬绑定
  foo.call(obj);
  console.log(`bar: ${this.a}`);
}
bar(); // foo: 2 bar: 3
setTimeout(bar, 100); // foo: 2  bar: node环境中是undefined,浏览器中是3
// 硬绑定的bar不可能再修改它的this
bar.call(global); // foo: 2 bar: 3
bar.call(window); // foo: 2 bar: 3