this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。
this绑定规则:
- 默认绑定,this指向全局对象window,严格模式下指向undefined
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
- 隐式绑定,由上下文对象调用?绑定到那个上下文对象。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
- 显示绑定,由 call 或者 apply (或者 bind )调用?绑定到指定的对象。
function foo(str1, str2) {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj, 'aa', 'aa2' ); // 2
- 硬绑定:一种显式的强制绑定,典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有值
硬绑定是一种常用的模式,ES5 中提供了内置的方法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
Function.prototype.bind
为什么要在 new 中使用硬绑定函数呢?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 进行初始化时就可以只传入其余的参数。 bind(..)可以对参数进行柯里化,就是可以把除了第一个参数(第一个参数用于绑定 this )之外的其他参数都传给下层的函数(这种技术称为“部分应用”,是“柯里化”的一种)function foo(p1,p2) { this.val = p1 + p2; } // 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么 // 反正使用 new 时 this 会被修改 var bar = foo.bind( null, "p1" ); var baz = new bar( "p2" ); baz.val; // p1p2
- API调用的“上下文”第三方库的许多函数,以及 JavaScript 语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和 bind(..) 一样,确保你的回调函数使用指定的 this 。
function foo(el) { console.log( el, this.id ); } var obj = { id: "awesome" }; // 调用 foo(..) 时把 this 绑定到 obj [1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome
- new绑定,绑定到新创建的对象
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
优先级: new绑定 -> 显示绑定 -> 隐式绑定 -> 默认绑定
被忽略的 this
如果函数并不关心 this 的话,你仍然需要传入一个占位值,这时 null
可能是一个不错的选择
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 把数组“展开”成参数
foo.apply( null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3
然而,总是使用 null 来忽略 this 绑定可能产生一些副作用。如果某个函数确实使用了this (比如第三方库中的一个函数),那默认绑定规则会把 this 绑定到全局对象(在浏览器中这个对象是 window ),这将导致不可预计的后果
优化:
Object.create(null) 和 {} 很像,但是并不会创建 Object.prototype 这个委托,所以它比 {} “更空”
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 我们的 DMZ 空对象
var _null = Object.create( null );
// 把数组展开成参数
foo.apply( _null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( _null, 2 );
bar( 3 ); // a:2, b:3
this词法:ES6中的箭头函数
ES6中的箭头函数,箭头函数不使用 this 的四种标准规则,而是根据当前的词法作用域来决定this ,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这其实和 ES6 之前代码中的 self = this 机制一样。
我们来看看箭头函数的词法作用域:
function foo() {
// 返回一个箭头函数
return (a) => {
//this 继承自 foo()
console.log( this.a );
};
}
var obj1 = {
a:2
};
var obj2 = {
a:3
};
var bar = foo.call( obj1 ); // foo() 的 this 绑定到 obj1
bar.call( obj2 ); // 2, 不是 3 ! 箭头函数的绑定无法被修改
foo() 内部创建的箭头函数会捕获调用时 foo() 的 this 。由于 foo() 的 this 绑定到 obj1 ,bar (引用箭头函数)的 this 也会绑定到 obj1 ,箭头函数的绑定无法被修改。( new 也不行!)
箭头函数最常用于回调函数中,例如事件处理器或者定时器:
function foo() {
setTimeout(() => {
// 这里的 this 在此法上继承自 foo()
console.log( this.a );
},100);
}
var obj = {
a:2
};
foo.call( obj ); // 2
网友评论