一、箭头函数中的this
在标准函数中,this引用的是把函数当成方法调用的上下文对象。但是在箭头函数中,this引用的是定义箭头函数的上下文,或者理解为它的父级作用域中的this。箭头函数中的this会保留定义该函数时的上下文
//标准函数中
window.color = 'red';
let o = {
color:'blue';
};
function sayColor() {
console.log(this.color);
}
sayColor(); //'red'
//直接调用sayColor()函数,这里this指向window,this.color = window.color
o.sayColor = sayColor;
//把sayColor赋值给o,相当于给o增加一个sayColor属性,属性为sayColor()函数方法
o.sayColor(); //'blue'
//这里this指向对象o this.color = o.color
//箭头函数中
window.color = 'red';
let o = {
color:'blue';
};
let sayColor = () => console.log(this.color);
//箭头函数在window上下文定义,this会保留为window上下文
sayColor(); //'red' 在window下调用
o.sayColor = sayColor;
o.sayColor(); //'red'
//this仍保留为定义箭头函数时的window上下文
二、 this的默认绑定
当一个函数没有明显调用对象的时候,即单纯作为独立函数调用的时候,将对函数的this使用默认绑定:绑定到全局的window对象。可以把这条规则看作无法应用其他规则时的默认规则。
凡是函数作为独立函数调用,无论它的位置在哪里,它的行为表现,都和直接在全局环境中调用无异
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2
//foo()直接使用不带任何修饰的函数进行调用,所以只能使用默认绑定
注意:如果使用严格模式,那么全局对象将无法使用默认绑定,因此 this 会绑定到undefined。
三、隐式绑定
当函数被一个对象“包含”的时候,我们称函数的this被隐式绑定到这个对象里面了。
隐式绑定下,作为对象属性的函数,对对象来说是独立的;定义在对象内部的函数只是**“恰好可以被这个对象调用**”而已。不应该认为这个函数“生来就是为了这个对象所调用的”。
函数虽然被定义在对象的内部中,但它和“在对象外部声明函数,然后在对象内部通过属性名称的方式取得函数的引用”,这两种方式在性质上是等价的(而不仅仅是效果上)
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
//无论是直接在 obj 中定义还是先定义再添加为引用属性,这个函数严格来说都不属于 obj 对象
三、隐式丢失
被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象(或者 undefined 上,取决于是否是严格模式)。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"
注意!!这里,var bar = obj.foo;
虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()是一个不带任何修饰的函数调用,所以应用默认绑定。
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// fn 其实引用的是 foo
fn(); // <-- 调用位置!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值
四、显示绑定
我们可以使用call(…)和apply(…)方法,在某个对象上强制调用函数
call的基本使用方式: fn.call(object,(参数1,参数2…))
fn是要调用的函数,object是this所绑定的对象。
注意:通过call()向函数传参时,必须把参数一个一个列出来
apply的基本使用方式:fn.apply(object,([Array]))
fn是要调用的函数,object是this所绑定的对象。第二个参数:可选,可以是Array的实例
call() 和 apply() 区别:call() 方法分别接受参数。apply() 方法接受数组形式的参数。如果要使用数组而不是参数列表,则 apply() 方法非常方便。
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
foo.call( obj ); //2
使用call()和apply()的好处是可以将任意对象设置为任意函数的作用域,这样对象可以不用关心方法。但是,显示绑定仍然无法解决丢失绑定的问题。
但是显示绑定到一个变种——硬绑定可以解决这个问题
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。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。
出于同样目的,ES5中新定义了一个内置方法 bind(),bind()方法会创建一个新的函数实例,其this值会被绑定到传给bind()的对象。
window.color = 'red';
var o = {
color:'blue'
};
function sayColor() {
console.log(this.color);
}
let objectSayColor = sayColor.bind(o);
objectSayColor(); //'blue'
在sayColor()上调用bind()并传入对象o创建了一个新函数objectSayColor()。objectSayColor()中的this值被设置为o。所以直接调用这个函数,即使是在全局作用域中调用,也会返回’blue’。
本文参考资料:
《你不知道的JavaScript(上卷)》
《JavaScript高级程序设计(第四版)》
函数中的this的四种绑定形式 — 大家准备好瓜子,我要讲故事啦~~