为什么要使用 this
函数间使用标识符,需要考虑上下文对象,我们可以在每个函数中都设置一个参数,通过这个表示上下文对象的参数,来使用对应的值。
然而,this 提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将 API 设计得更加简洁并且易于复用。
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, I'm KYLE
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, 我是READER
this 的机制
this 是在运行时进行绑定的,并不是在编写时绑定的,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this 就是这个记录的一个属性,会在函数执行的过程中用到。
调用位置
在理解 this 的绑定过程之前,首先要理解调用位置:调用位置就是函数在代码中被调用的位置(而不是被声明的位置)。
默认绑定、隐式绑定和显式绑定
当代码中的函数是直接使用不带任何修饰的函数引用进行调用的,就只能使用默认绑定,无法应用其他规则。默认绑定指的是this默认指向调用这个方法的上下文环境。
function foo() {
console.log(this.a);
}
var a = 2;
foo(); // 2
当函数引用由上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。
function foo() {
console.log(this.a);
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
obj1.foo(); // 2
隐式绑定会有许多种意想不到的“特殊”情况,例如:声明对象内部函数,却在全局中调用声明变量时,对象内部函数的 this 会指向全局上下文;回调函数中调用的外部方法,外部方法中的 this 指向的却是回调函数内部的上下文。
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"
我们也可以使用 fn.call(ctx) 或者 fn.apply(ctx) 实现显式绑定。显示绑定会将fn函数内部的this指针强制指向ctx上下文。
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
通常情况下,我们可以使用 fn.call(null)来取消上下文的指定,但是使用null还是不太安全,因为null并不是完全的“空”。我们可以通过 Object.create(null)来创建一个完全的空,Object.create(null)和{}很像,但是并不会创建Object.prototype这个委托,所以它比{}“更空”。
在多个call或者apply嵌套调用的情况下,ctx使用的是最内层的call或者apply所赋予的上下文。
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!