【JavaScript面试题】之this全面解析

131 阅读4分钟

创作不易,请大家多多点赞👍、评论、收藏哦!
面试中经常出现的this问题,我们今天就一起来总结下,把this的面试题一网打尽! 让我们来看看在函数执行过程中调用位置如何决定this的绑定元素的;

四种绑定规则

1.默认绑定

function foo(){
  console.log(this.a)
}
var a = 2
foo()

大家看下这段代码,应该都不陌生吧,请问第二行的log会打印出来什么?当调用foo()时,this是默认绑定,指向的全局变量,所以打印出2。

function foo() {
  "use strict";
  console.log(this.a);
}
var a = 2;
foo();

如果在foo中加上"use strict"呢?这里有一个细节需要注意,只有在foo()运行在非strict mode下时,默认绑定才能绑定到全局对象,因此这段代码log是会报TypeError错的。

2.隐式绑定

function foo() {
  console.log(this.a);
}
var obj = {
  a: 2,
  foo,
};
obj.foo();

当foo()被调用时,它前面有obj;隐式绑定会把this绑定在obj上,因此会打印出2。

function foo() {
  console.log(this.a);
}
var obj2 = {
  a: 42,
  foo: foo,
};
var obj1 = {
  a: 2,
  obj2,
};
obj1.obj2.foo();

如果foo()前面有多个对象呢?多个调用对象,起作用的是离foo()最近的那个,也就是说会打印出42。

隐式丢失

function foo() {
  console.log(this.a);
}
var obj = {
  a: 2,
  foo: foo,
};
var bar = obj.foo;
var a = "global";
bar();

这是常见的隐式丢失,被隐式绑定的方法会丢失绑定对象;bar是obj.foo的一个引用,实际还是foo方法本身,bar()实际应用了默认绑定,第二行会打印出global;

function foo() {
  console.log(this.a);
}
function doFoo(fn) {
  // fn方法调用的地方
  fn();
}
var obj = {
  a: 2,
  foo: foo,
};
var a = "global";
doFoo(obj.foo);

当我们给doFoo方法传入函数时也会被隐式赋值,fn()被调用时会被应用默认绑定,结果和上个例子一样,也会打印出global;

3.显示绑定

JS提供的call、apply可以去改变this的指向,我们上面的隐式丢失也可以通过call、apply硬绑定的方式来解决;

function foo() {
  console.log(this.a);
}
var obj = { a: 2 };
var bar = function () {
  foo.call(obj);
};
bar();

来看上面这个例子,创建了bar()方法,在内部用foo.call(obj)调用了foo方法,强制将foo的this绑定到obj,无论之后怎么调用bar,它总会手动在obj上调用foo,因此会打印2。

4.new绑定

使用new来调用函数,会执行下面的操作。

001.jpeg

function foo(a) {
  this.a = a;
}
var bar = new foo(2);
console.log(bar.a);

在上面这个例子中,使用new来调用foo()时,我们会构造一个新对象并把它绑定到foo()调用的this上,因此会打印2。

优先级比较

我们已经知道了四种thi绑定的方式,那它们的优先级是什么样呢?毫无疑问,默认绑定的优先级是最低的,那我们先看一下隐式绑定和显式绑定哪个优先级高?

function foo() {
  console.log(this.a);
}
var obj1 = {
  a: 2,
  foo: foo,
};
var obj2 = {
  a: 3,
  foo: foo,
};
obj1.foo();//2
obj2.foo();//3

obj1.foo.call(obj2);//3
obj2.foo.call(obj1);//2

可以看到,显示绑定的优先级更高。现在我们需要看一下new绑定和隐式绑定的优先级谁高?

function foo(something) {
  this.a = something;
}
var obj1 = {
  foo: foo,
};
var obj2 = {};
obj1.foo(2);
console.log(obj1.a); // 2 
obj1.foo.call(obj2, 3);
console.log(obj2.a); // 3

var bar = new obj1.foo(4);
console.log(obj1.a); // 2
console.log(bar.a); // 4

可以看到new绑定比隐式绑定优先级更高。但是new绑定和显式绑定谁的的优先级更高呢?

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

var obj1 = {};
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2

var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3

上面的例子bar被硬绑定到obj1上,但new bar(3)并没有像我们预计的那样把obj1.a修改为3;相反new修改了之前bar的this;使用new之后,新对象baz,baz.a的值为3。 因此我们知道了优先级的排名,new>显示绑定>隐式绑定>默认绑定;

箭头函数

除了这四种this绑定规则,ES6中还介绍了一种特殊的函数类型:箭头函数;它的this是根据外层(函数或全局)的作用域来决定的;

var obj = {
  name: 'obj',
  foo1: () => {
    console.log(this.name)
  },
  foo2: function () {
    console.log(this.name)
    return () => {
      console.log(this.name)
    }
  }
}
var name = 'window'
obj.foo1()
obj.foo2()()

箭头函数内的this是由外层作用域决定的

  • 对于obj.foo(),它的外层是window,对象obj不是作用域,(作用域分为全局作用域window和局部作用域),所以会打印window;
  • obj.foo2()(),首先会执行obj.foo(),它是普通函数,所以它的this是调用它的obj,因此打印出obj,而它返回的是一个箭头函数,它的this由外层作用域决定,也就是foo2,那它的this也是obj;