this的四种绑定规则
JavaScript 的 this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。
1.作为普通函数调用–默认绑定
当函数作为普通函数调用,也就是不作为某个对象的属性调用时,函数中的this指向全局对象window。
window.name = 'globalName';
var getName = function(){
return this.name;
};
console.log( getName() ); // globalName
window.name = 'globalName';
var myObject = {
name: 'sven',
getName: function(){
return this.name;
}
};
var getName = myObject.getName;
//将getName()方法传递给变量getName,此时getName()方法还未执行。
console.log( getName() ); // globalName 当执行的时候,getName()是作为普通函数调用的。函数的执行环节是window对象。
console.log(myObject.getName()); //sven。 执行环境是myObject对象。
事件处理程序中使用this
var btn = document.getElementById("btn");
btn.onclick = function eve(){
alert(this.id); //btn
function callback(){
alert(this);
}
callback(); //window
}
以上代码,事件处理程序中eve()中的this,指向它的调用者,也就是btn。但是回调函数callback()是在事件处理程序内部执行的,它并没有被任何对象调用,其this指向window。
如果想让callback的this指向btn,可以用一个变量保存对btn的引用:
btn.onclick = function eve(){
var that = this;
function callback(){
alert(that.id);
}
callback(); //btn
}
严格模式下
如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined:
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo(); // Uncaught TypeError: Cannot read property 'a' of undefined
2.作为对象的方法调用–隐式绑定
当函数作为对象的方法被调用时,this 指向该对象:
var obj = {
a: 1,
getA: function(){
alert ( this === obj ); // true
alert ( this.a ); // 1
}
};
obj.getA();
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2 调用foo()时,this指向obj
以上代码,当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。调用 foo() 时 this 被绑定到 obj,因此 this.a 和 obj.a 是一样的。
对象属性引用链中只有最顶层影响调用位置。
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42 this指向obj2
隐式丢失
一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上(严格模式)。
var a = "oops, global"; // a 是全局对象的属性
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
bar(); // "oops, global"
var bar = obj.foo;
相当于:
var bar = function{
console.log( this.a );
}
//将obj的foo属性传递给变量bar,此时foo()函数并没有执行,执行将函数体给到变量bar。
以上代码,虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,应用了默认绑定。
3.Function.prototype.call 或 Function.prototype.apply 调用–显式绑定
隐式绑定时,必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把 this 间接(隐式)绑定到这个对象上。
如果不想在对象内部包含函数引用,而想在某个对象上强制调用函数,可以使用call()和apply()方法。它们会把这个对象绑定到this,接着在调用函数时指定这个 this。这种方式称为显示绑定。
var obj1 = {
name: 'sven',
getName: function(){
return this.name;
}
};
var obj2 = {
name: 'anne'
};
console.log( obj1.getName() ); // sven
console.log( obj1.getName.call( obj2 ) ); //anne ,call()方法改变this指向,使getName中的this指向obj2.
function foo() {
console.log( this.a );
}
var obj = {a:2};
foo.call( obj ); // 2
// 通过 foo.call(),在调用 foo 时强制把它的 this 绑定到 obj 上。
定时器内含有this
function foo() {
setTimeout(function(){
console.log( this.a );
},3000)
}
var a = 3;
var obj = { a:2};
foo.call( obj ); // 3
以上代码中call()无法改变this的指向。3s后才调用定时器,但是定时器是window对象的方法,所以this还是指向定时器的调用者window。
解决方式1:
function foo() {
var that = obj;
setTimeout(function(){
console.log( that.a );
},3000)
}
var a = 3;
var obj = {a:2};
foo(); // 2
解决方式2:
function foo() {
setTimeout(function(){
console.log( this.a );
}.bind(obj),3000)
}
var a = 3;
var obj = {a:2};
foo(); // 2
解决方式3:
function foo() {
setTimeout(() =>{
console.log( this.a );
},3000)
}
var a = 3;
var obj = {a:2};
foo.call(obj); // 2
硬绑定
function foo() {
console.log( this.a );
}
var obj = { a:2 };
var bar = function() {
foo.call( obj );
};
bar(); // 2
var bar = function() {
foo.call( obj );
};
相当于:
var bar = function{
console.log( obj.a );
}
//使用call()方法时,foo内部的this就已经执行了obj。
ES5中提供了内置的bind()方法来实现硬绑定,
bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文。
function foo() {
console.log( this.a );
}
var obj = { a:2 };
var bar = foo.bind( obj );
bar(); // 2
API调用的上下文
JavaScript 语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和 bind(..) 一样,确保回调函数使用指定的 this。
function foo(el) {
console.log( el, this.id );
}
var obj = { id: "awesome" };
[1, 2, 3].forEach( foo, obj ); // 调用 foo(..) 时把 this 绑定到 obj
// 1 awesome 2 awesome 3 awesome
这些函数实际上就是通过 call(..) 或者 apply(..) 实现了显式绑定。
4.构造器调用–new绑定
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
以上代码,使用 new 来调用 foo(..) 时,会构造一个新对象并把它绑定到 foo(..) 调用中的 this上。
var MyClass = function(){
this.name = 'sven';
};
var obj = new MyClass();
alert ( obj.name ); // sven
如果构造函数返回了一个引用类型的值,最终的运算结果就会返回这个值:
var MyClass = function(){
this.name = 'sven';
return { // 显式地返回一个对象
name: 'anne'
}
};
var obj = new MyClass();
alert ( obj.name ); //:anne