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对象。
undefined
,函数内部调用全局变量a直接报错!
隐式绑定
隐式绑定需要考虑有无上下文对象的情况。当函数引用有上下文对象时,隐式绑定就会把this绑定到这个上下文对象中。
var a = 0 function fun() { console.log(this.a); } let obj = { a: 1, fun: fun, } obj.fun() // 1
函数fun
在全局声明,它作为引用属性被添加到obj
中。 严格来说,不管fun
函数在全局声明还是在obj
的fun
中声明,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》上券