本文解析JavaScript中this的隐式绑定机制,指出this指向取决于函数调用时的调用域(call-site)而非声明位置。通过调用栈示例说明函数嵌套调用时this的指向变化,并分析函数引用赋值导致的隐式丢失现象,揭示其最终绑定至全局对象的原理。
this隐式绑定与调用域基础
首先通过一个具体示例来展开讨论。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
在此场景中,this指向了obj对象。原因在于foo函数执行时的调用域(call-site,即调用发生的作用域)位于obj内部。需要特别注意的是,this的指向取决于函数运行时的调用位置,与其在代码中的声明位置无关。
调用域与调用栈的关系
接下来探讨调用域与调用栈的概念。
这里将调用域简称为call-site,调用栈称为call-stack。借助以下代码示例,可以更直观地理解这两个概念之间的关系。
function baz() {
// call-stack is: `baz`
// so, our call-site is in the global scope
console.log( "baz" );
bar(); // <-- call-site for `bar`
}
当在baz()函数内部调用bar()时,bar的调用域即为baz函数的作用域,此时调用栈中仅包含baz。由于baz函数本身暴露在全局作用域中,因此其自身的调用域也处于全局作用域内。
function bar() {
// call-stack is: `baz` -> `bar`
// so, our call-site is in `baz`
console.log( "bar" );
foo(); // <-- call-site for `foo`
}
function foo() {
// call-stack is: `baz` -> `bar` -> `foo`
// so, our call-site is in `bar`
console.log( "foo" );
}
baz(); // <-- call-site for `baz`
在理解上述机制后,重新审视开头的示例,逻辑应当更加清晰。本质上,this仅仅指向了其所在的调用域。
函数引用的隐式丢失
此外,还存在另一种常见的操作模式。
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
Implicitly Lost(隐式丢失)
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // function reference/alias!
var a = "oops, global"; // `a` also property on global object
bar(); // "oops, global"
尽管bar引用了obj对象上的foo方法,但实际上这等同于直接对foo函数进行引用。因此,在单独调用bar()时,this会按照默认规则绑定到全局对象上。
function foo() {
console.log( this.a );
}
function doFoo(fn) {
// `fn` is just another reference to `foo`
fn(); // <-- call-site!
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // `a` also property on global object
doFoo( obj.foo ); // "oops, global"