此篇为《你不知道的JavaScript》读书笔记
函数的执行过程中调用位置决定this的绑定对象
绑定规则
默认绑定
- 在非严格模式中
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
在代码中,foo()是直接使用不带修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则
- 如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此this会绑定到undefined
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo(); // TypeError: this is undefined
隐式绑定
- 当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
case 1:
调用位置会使用obj上下文来引用函数,因此你可以说函数被调用时obj对象“拥有”或者“包含”它。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
因为调用foo()时this被绑定到obj,因此this.a和obj.a是一样的。
case 2:
这解答了我的一个困惑,在读Axios源码的时候,有这样一段代码
Axios.prototype.request = function request(config) {
……
}
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
调用方式
import axios from 'axios';
Vue.prototype.axios = axios;
created(){
this.axios.post('/api/posta');
}
这段代码很简单,如果调用方式为axios.post,则这个post方法其实是request方法的引用。
困惑就是,这个this.request
为什么可以引用到挂在原型上的request
方法。
解答:

看右边的this
,为Axios
。
调用post
时,this
被绑定到了Axios
,所以可以直接用this.request
case 3:
对象属性引用链中只有最顶层或者说最后一层会影响调用位置。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a是全局对象的属性
bar(); // "oops, global”
- 隐式丢失
case 1:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a是全局对象的属性
bar(); // "oops, global”
虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
case 2:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a是全局对象的属性
setTimeout( obj.foo, 100 ); // "oops, global”
为什么从setTimeout的调用结果看来,fn中的this是全局的呢?
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值。
而JavaScript环境中内置的setTimeout()
函数实现和下面的伪代码类似:
function setTimeout(fn,delay) {
// 等待delay毫秒
fn(); // <-- 调用位置!
}
就像我们看到的那样,回调函数丢失this绑定是非常常见的。
除此之外,还有一种情况this的行为会出乎我们意料:调用回调函数的函数可能会修改this。在一些流行的JavaScript库中事件处理器常会把回调函数的this强制绑定到触发事件的DOM元素上。这在一些情况下可能很有用,但是有时它可能会让你感到非常郁闷。
显式绑定
可以使用call(..)和apply(..)方法来显示绑定this。
- 硬绑定
硬绑定的典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有值:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a:2
};
var bar = function() {
return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5
由于硬绑定是一种非常常用的模式,所以在ES5中提供了内置的方法Function.prototype.bind,它的用法如下:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
var obj = {
a:2
};
“var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
new绑定
使用new
来调用函数,即这种函数调用被称为构造函数调用。
在new的过程中会发生以下事情:
- 创建一个全新的对象
- 这个对象会被执行[[原型]]连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个对象
具体的在笔者的https://www.jianshu.com/p/90ec658aa0e0里面有写。
思考以下例子
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
使用new来调用foo(..)时,我们会构造一个新对象并把它绑定到foo(..)调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。
优先级
上述的这些绑定的优先级为:new绑定,显示绑定,隐式绑定,默认绑定。
判断this
现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的顺序来进行判断:
- 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
var bar = new foo()
- 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。
var bar = foo.call(obj2)
- 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。
var bar = obj1.foo()
- 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到
undefined
,否则绑定到全局对象。
var bar = foo()
就是这样。对于正常的函数调用来说,理解了这些知识你就可以明白this的绑定原理了。不过……凡事总有例外。
绑定例外
如果你把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则:
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2”
箭头函数
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!
foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this 也会绑定到obj1,箭头函数的绑定无法被修改。(new也不行!)
如果我们把箭头函数换成普通的function
,则结果为3。
网友评论