this到底是什么
this是在运行时进行绑定的,不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(执行上下文),这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。
调用位置
寻找函数的调用位置最重要的是分析调用栈(为了到达当前执行位置所调用的所有函数)。调用位置就在当前正在之前的函数的前一个调用中。调用位置决定了 this 的绑定对象。
function a() {
// 当前调用栈是:a
// 因此,当前调用位置是全局作用域
console.log('a');
b(); // <-- b的调用位置
}
function b() {
// 当前调用栈是a -> b
// 因此,当前调用位置是在a中
console.log('b');
c(); // <-- c的调用位置
}
function c() {
// 当前调用栈是 c -> b -> a
// 因此,当前调用位置在b中
console.log('c');
}
a(); // <-- a的调用位置
绑定规则
- 默认绑定
function f() {
console.log(this.a);
}
var a = 2;
f(); // 2
声明在全局作用域中的变量a
就是全局对象的一个同名属性,我们调用f()的时候,因为函数调用时应用了this
的默认绑定,因此this.a
被解析成了全局变量a
,this
指向了全局对象
!!!如果使用严格模式,全局对象无法使用默认绑定,this
会绑定到undefined
- 隐式绑定
需要考虑的另外一条规则是:调用位置是否有上下文对象,或者是否被某个对象拥有或者包含。
function f() {
console.log(this.a);
}
var obj = {
a: 2,
f: f
};
obj.f(); // 2
函数被调用时obj
对象“拥有”f
函数。
当函数引用有上下文对象时,隐式绑定规则
会把函数调用中的this
绑定到这个上下文对象,因为调用f()
时this
被绑定到obj
,所以this.a
和obj.a
是一样的。
对象属性引用链中只有最后一层会影响调用位置:
function f() {
console.log(this.a);
}
var obj2 = {
a: 42,
f: f
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.f(); // 42
- 隐式丢失
一个常见的this
绑定问题就是被隐式绑定
的函数会丢失绑定对象
,会用默认绑定
,导致把this
绑定到全局对象
或者undefined
上,取决于是否是严格模式
。
function f() {
console.log( this.a );
}
var obj = {
a: 2,
f: f
};
var bar = obj.f; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global" 其实是一个不带任何修饰的函数调用,应用了默认绑定
- 显示绑定
隐式绑定必须在对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,把this
间接绑定到这个对象上。
function foo() {
console.log(this.a);
}
var obj = {
a: 2
}
var a = 666;
foo(); // 666
foo.call(obj); // 2 调用foo时强制把它的this绑定到obj上
- 硬绑定
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
使用bind
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
var obj = {
a: 2
};
var bar = foo.bing(obj);
var b = bar(3); // 2 3
console.log(b); // 5
bind(..)
会返回一个硬编码的新函数,它会把参数设置为this
的上下文并调用原始函数。
4. new绑定
JavaScript中的“构造函数”只是一些使用new
操作符时被调用的函数,并不属于某个类,也不会实例化一个类,只是被new
操作符调用的普通函数。
包括内置对象函数在内的所有函数都可以用new
来调用,这种函数调用被称为构造函数调用:实际上并不存在“构造函数”,只有对于函数的“构造调用
”
使用new来调用函数,或着说发送构造函数调用时,会自动执行以下操作:
- 创建(构造)一个全新的对象。
- 这个新对象会被执行[[原型]]连接。
- 这个新对象会绑定到函数调用的
this
。 - 如果函数没有返回其他对象,
new
表达式中的函数调用会自动返回这个新对象。
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
绑定的是新创建的对象
var bar = new foo();
- 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是,
this
绑定的是指定的对象。
var bar = foo.call(obj2);
- 函数是否在某个上下文对象中调用(隐式绑定)?如果是,
this
绑定的是那个上下文对象。
var bar = obj1.foo();
- 如果都不是,使用默认绑定,如果在严格模式下,就绑定到
undefined
,否则就绑定全局对象
var bar = foo();
结论
- 由
new
调用?绑定到新创建的对象。 - 由
call
或apply
(或bind
)调用?绑定到指定对象。 - 由上下文对象调用?绑定到那个上下文对象。
- 默认:严格模式下绑定
undefined
,否则绑定到全局对象。