这个this的解析

1,771 阅读4分钟

this 的理解

首先得理解this是在函数被调用的时候绑定的,完全取决于函数的调用位置。这与静态作用域相反,反而有点类似动态作用域(由运行时决定)。this在运行时才进行对象绑定。

其次,就是我们常见的this指向问题,即this的绑定。

this的绑定规则

默认绑定

大家可能会好奇默认绑定是什么?它就是非严格模式下,函数在全局环境中运行,函数内部的this指向的是window对象。

var a = '我是全局环境下的a';
function fun() {
  console.log(this);
  console.log(this.a);
}
fun()
// Window对象
// 我是全局环境下的a

从fun函数中输出的this就可以看出,全局下的独立函数运行,内部的this绑定的是window对象。

image.png 严格模式下,函数内部调用this为undefined,函数内部调用全局变量a直接报错!

隐式绑定

隐式绑定需要考虑有无上下文对象的情况。当函数引用有上下文对象时,隐式绑定就会把this绑定到这个上下文对象中。

var a = 0
function fun() {
  console.log(this.a);
}
let obj = {
  a: 1,
  fun: fun,
}
obj.fun() // 1

函数fun在全局声明,它作为引用属性被添加到obj中。 严格来说,不管fun函数在全局声明还是在objfun中声明,fun这个函数都不属于obj对象。

主要是看函数被调用时的上下文对象。

上例中:函数在obj内调用,即obj.fun()。此时fun()的上下文对象为obj,this指向字面量obj。所以输出的是obj内部的a变量。

var a = 0
function fun() {
  console.log(this.a);
}
let obj = {
  a: 1,
  fun: fun,
}
let o = obj.fun
o() // 0

此例,将obj.fun用一个全局变量保存,之后再运行该全局变量。运行时,上下文对象为全局的window。所以fun函数中的this,指向window,输出全局的a变量0。当然此例需要在非严格模式下运行,否则this绑定的对象丢失,则为undefined。

显式绑定

如果想要强制地在某个对象中调用函数,将this绑定到这个对象中。我们可以使用call()apply(),传入想要绑定的this的对象。直接指定this所指对象。

let a = 'window 中的 a'
function fun() {
  console.log(this.a);
}
let obj = {
  a: 'obj 中的 a '
}
fun.call(obj) // obj 中的 a 
fun.apply(obj) // obj 中的 a 

两者第一个参数都是this所指的对象。call()apply()的区别在于传入的参数不同,call()方法分别接受参数,而apply()方法接受数组形式的参数。后续会详细讲解call()apply()的实现。

new绑定

构造函数:指使用new操作符时被调用的普通函数。它不属于某个类,也不会实例化一个类。 需要特别注意的是,不存在所谓的构造函数,只有对于函数的构造调用

js中new的运行机制(new的过程):
1、构造一个全新的对象
2、这个新对象会被执行[[prototype]]连接(包含原型、原型链)
3、绑定到函数调用的this
4、如果函数没有其他返回对象,那么new表达式中的函数调用会自动返回这个新对象。

在此,我们主要讨论的是this。

function Fun(a) {
  this.a = a
}
var obj = new Fun(2)
console.log(obj.a);  // 2

使用new绑定,obj调用Fun(),此时this指向obj。所以在obj变量中包含了a属性。obj.a输出为2。

绑定优先级

毫无疑问,默认绑定的优先级肯定是最低的。

隐式绑定和显式绑定比较

let a = 'window 中的 a'
function fun() {
  console.log(this.a);
}
let obj = {
  a: 'obj 中的 a ',
  fun: fun
}
let test = {
  a: 'test 中的 a'
}
obj.fun() // obj 中的 a 
obj.fun.call(test) // test 中的 a

call改变了obj中fun函数中的this,将this.a指向了test中的a属性。所以显式绑定的优先级高于隐式绑定。

隐式绑定和new绑定比较

function fun(a) {
  this.a = a
  console.log(this.a);
}
let obj = {
  a: 'obj 中的 a ',
  fun: fun
}
obj.fun(obj.a) // obj 中的 a 
let o = new obj.fun('o 中的 a') // o 中的 a

new obj.fun('o 中的 a')中,new将原本this绑定的obj,转移到了o变量上。并给o内部的a属性赋值,即o 中的 a

显示绑定和new绑定比较

无法通过new fun.call(obj)直接进行测试,但是可以通过bind绑定来测试。

function fun(a) {
  this.a = a
  console.log(this);
}
var obj = {}
var bar = fun.bind(obj)
bar(2)
console.log(obj.a);
var baz = new bar(3)
console.log(baz.a);
// 结果
// {a: 2}
// 2
// fun {a: 3}
// 3

使用bind绑定后,this指向了obj,可以看到输出的this为{a: 2}。当new时,修改了bind绑定,将this指向了baz。因此我们得到了baz这个新对象。

总结:

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

箭头函数

箭头函数不会根据上面this的绑定规则,而是根据词法作用域(静态作用域)决定this的。箭头函数会继承外层函数调用的this绑定。与使用一个变量保存this机制一样。

 function fun() {
  setTimeout(() => {
    console.log(this);
  }, 0)
  setTimeout(function () {
    console.log(this);
  }, 0)
}
let obj = {
  a: "obj 中的 a",
  fun: fun
}
obj.fun()
// {a: "obj 中的 a", fun: ƒ}
// Window

从上例中可以看出,使用箭头函数的第一个setTimeout中this指向的是obj。也可以将箭头函数理解成函数表达式,直接使用fun所指的this。

参考书籍:《你不知道的JavaScript》上券