这是我参与更文挑战的第4天,活动详情查看: 更文挑战。
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对象。
严格模式下,函数内部调用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
函数在全局声明还是在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绑定到这个对象中。我们可以使用cal()
、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所指的对象。cal()
和apply()
的区别在于传入的参数不同,cal()
方法分别接受参数,而apply()
方法接受数组形式的参数。后续会详细讲解cal()
、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》上券