这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
1.为什么要用this?
- this提供了一种更优雅的方式来隐式传递 一个对象引用。
function identify() {
return this.name.toUpperCase();
}
function speak() {
var greeting = "Hello, I'm " + identify.call( this );
console.log( greeting );
}
var me = {
name: "Kyle"
};
var you = {
name: "Reader"
};
identify.call( me ); // KYLE
identify.call( you ); // READER
speak.call( me ); // Hello, 我是 KYLE
speak.call( you ); // Hello, 我是 READE
这段代码可以在不同的上下文对象(me 和 you)中重复使用函数 identify() 和 speak(), 不用针对每个对象编写不同版本的函数。
如果不使用 this,那就需要给 identify() 和 speak() 显式传入一个上下文对象。
function identify(context) {
return context.name.toUpperCase();
}
function speak(context) {
var greeting = "Hello, I'm " + identify( context );
console.log( greeting );
}
identify( you ); // READER
speak( me ); //hello, 我是 KYLE
2. this四条绑定规则
this 关键字, 被自动定义在所有函数的作用域中。
注意:this的指向在函数创建时确定不了,被调用时才能确定。
我们在找this指向时,要先找到调用位置,然后判断符合哪一条绑定规则,同时注意绑定规则的优先级。
2.1 默认绑定
在找不到函数的调用对象时,
- 非严格模式:函数中this会指向全局对象window。
- 严格模式下,函数中this会指向 undefined。
注意: 对于默认绑定来说,决定 this 绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。
例1:函数调用前面没有对象
function sayHi(){
console.log('Hello,', this.name);
}
var name = 'YvetteLau';
sayHi();
// 'Hello,' YvetteLau
2.2 隐式绑定
通过对象调用函数,那么函数中this会指向最后调用它的对象。
隐式丢失
1.将obj.fn()赋值给某个变量
虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
如下示例, o.foo 的 this 隐式绑定在了 o 对象上, 而 bar 引用了 o.foo 函数本身, 所以此时的 bar() 其实是一个不带任何修饰的函数调用, 因此使用了 默认绑定 规则:
var o = {
a: 1,
foo() {
console.log(this.a)
}
}
var bar = o.foo
o.foo() // 1
bar() // undefined
2.将obj.fn()赋值给函数的形参
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值。示例中, bar(o.foo) 实际上采用了隐式赋值: callback = o.foo, 事实上跟上面的例子一样, 都是直接引用了 o.foo 函数本身, 所以造成了 隐式丢失:
function bar(callback) {
callback()
}
var o = {
a: 1,
foo() {
console.log(this.a)
}
}
bar(o.foo) // undefined
3.setTimeOut函数中的this
例2:setTimeOut中包含的函数,在后面执行时,并没有对象去调用这个函数,所以也是默认绑定。
btn.onclick = function(){
setTimeout(function(){
console.log(this);
},0)
}
btn.onclick();
// window
虽然onclick函数是btn对象调用的,但setTimeout函数中的内容并没有在btn对象调用onclick函数时立即执行,等到setTimeout函数执行时,setTimeout函数找不到调用对象,这时变为默认绑定,非严格模式下,setTimeout函数内的this指向window。
(setTimeOut任务是宏任务,会在下一轮事件循环执行。具体参见:juejin.cn/post/693532…)
2.3 显示绑定
我们有时想要强制为某个函数绑定this,可以通过使用 call() 和 apply() 方法来实现;
函数.call(要绑定this的对象, 参数1, 参数2, ...)
函数.apply(要绑定this的对象, [参数1, 参数2, ...])
硬绑定
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的 bar 不可能再修改它的 this
bar.call( window ); // 2
我们创建了函数 bar(),并在它的内部手动调用 了 foo.call(obj) ,因此强制把 foo 的 this 绑定到了 obj。无论之后如何调用函数 bar,它 总会手动在 obj 上调用 foo。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。
硬绑定ES5API
bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。
硬绑定时传入null/undefined,则会忽略本次硬绑定,采用默认绑定规则。
2.4 new绑定
当使用 new 操作符调用某个函数时,这个函数被构造调用。
new 操作符在调用函数时做的工作:
- 创建或构造了一个全新的对象
- 这个新对象会被执行[[原型]]连接
- 函数中的
this会指向这个新对象 - 如果被调用的函数没有返回(return), 则
new表达式中的函数调用会自动返回这个新对象
3. 绑定规则的优先级
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
判断this:
- 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。
- 函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是 指定的对象。
- 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上 下文对象。
- 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。
4. 箭头函数
箭头函数无法使用上述规则。根据根据外层(函数或者全局)作用域来决定 this。
箭头函数的this指向它外层作用域调用时的this。
foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。由于 foo() 的 this 绑定到 obj1, bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也不行!)
function foo() {
// 返回一个箭头函数
return (a) => {
//this 继承自 foo()
console.log( this.a );
};
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是 3
箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。
参考:
你不知道的JavaScript