深入理解 JavaScript 中 this 关键字的绑定规则与特例
本文深入解析 JavaScript 中 this 关键字的核心机制。从全局上下文到函数调用,详细阐述 this 值由调用表达式形式决定的原理。重点分析引用类型、非引用类型、构造函数调用以及 call/apply 手动绑定等场景下的 this 指向变化,并揭示赋值、逗号等操作符导致 this 丢失引用类型而指向全局对象的特例。
摘要与目录
1.1.1 摘要
拥有 C++、C# 或 Java 等面向对象语言开发经验的开发者,对 this 关键字应当非常熟悉。JavaScript 同样作为一门面向对象语言,内置了 this 关键字。本文将系统介绍 JavaScript 中 this 的具体行为与规则。
本文目录
• 全局代码中的 this
• 函数中的 this
• 引用类型
• 函数调用以及非引用类型
• 引用类型以及 this 的 null 值
• 函数作为构造器被调用时 this 的值
• 手动设置函数调用时 this 的值
正文:this 的核心机制
1.1.2 正文
许多面向对象语言都包含 this 关键字,这容易让人将 this 与面向对象编程中的对象创建联系起来,通常认为它指向由构造器新创建的对象。但在 ECMAScript 标准中,this 的含义更为广泛:它不仅表示创建的对象,更是执行上下文(Execution Context)的一个属性。
代码如下:
activeExecutionContext = {
// Variable object.
VO: {...},
this: thisValue
}; 全局代码中的 this
全局代码中的 this
代码如下:
// Global scope
// The implicit property of
// the global object
foo1 = "abc";
alert(foo1); // abc
// The explicit property of
// the global object
this.foo2 = "def";
alert(foo2); // def
// The implicit property of
// the global object
var foo3 = "ijk";
alert(foo3); // ijk 通过上述显式和隐式定义的全局属性 foo1、foo2 和 foo3 可以看出,在全局代码中,this 的值即为全局对象本身(在浏览器环境中为 window 对象)。接下来将探讨函数作用域内 this 的行为。
函数中的 this
函数中的 this
当 this 出现在函数代码中时,其行为变得复杂且容易引发困惑。
函数中 this 值的首要特性(也是最重要的一点)是:它并非静态绑定在函数定义上。
如前所述,this 的值是在进入执行上下文(Execution Context)阶段确定的。在函数内部,每次调用时 this 的值都可能不同。
然而,一旦进入代码执行阶段,this 的值便固定不变。此时尝试给 this 赋予新值是不可能的,因为此时 this 已不再是变量。
接下来通过具体示例说明函数中 this 的行为。
首先定义两个对象 foo 和 person。foo 包含属性 name,而 person 包含属性 name 和方法 say()。具体定义如下:
代码如下:
// Defines foo object.
var foo = {
name: "Foo"
};
// Defines person object.
var person = {
name: "JK_Rush",
say: function() {
alert(this === person);
alert("My name is " + this.name);
}
};
person.say(); // My name is JK_Rush
// foo and person object refer to
// the same function say
foo.say = person.say;
foo.say(); // My name is Foo. 通过上述代码观察,调用 person.say() 时,this 指向 person 对象;当将 person.say 赋值给 foo.say 后,调用 foo.say() 时,this 指向的是 foo 对象而非 person。这是为何?
关键在于:函数中的 this 值是非静态的,其值在函数调用时、具体代码执行前确定。this 的具体指向由激活上下文的调用者(如外层上下文)决定,更确切地说,是由调用表达式的形式决定。因此,this 并非静态绑定在函数上。
既然 this 非静态绑定,是否可以在函数内部动态修改 this 的值?
代码如下:
// Defines foo object.
var foo = {
name: "Foo"
};
// Defines person object.
var person = {
name: "JK_Rush",
say: function() {
alert(this === person);
this = foo; // ReferenceError
alert("My name is " + this.name);
}
};
person.say(); // My name is JK_Rush 在方法 say() 中尝试动态修改 this 的值(如 this = foo),执行时会抛出 ReferenceError。这是因为一旦进入代码执行阶段(函数调用时,具体代码执行前),this 的值就已确定,不可更改。
引用类型
引用类型
前文提到 this 由调用表达式的形式决定,那么表达式的具体形式如何影响 this 的值?
首先介绍一个内部类型——引用类型(Reference Type)。其值可用伪代码表示为一个拥有两个属性的对象:base 属性(属性所属的对象)以及该 base 对象中的 propertyName 属性。
代码如下:
// Reference type.
var valueOfReferenceType = {
base: mybase,
propertyName : 'mybasepropertyName'
}; 引用类型的值仅可能出现在以下两种情况:
• 当处理一个标识符的时候
• 或者进行属性访问的时候
标识符包括变量名、函数名、函数参数名以及全局对象的未受限属性。
代码如下:
// Declares varible.
var foo = 23;
// Declares a function
function say() {
// Your code.
} 中间过程中,对应的引用类型如下:
代码如下:
// Reference type.
var fooReference = {
base: global,
propertyName: 'foo'
};
var sayReference = {
base: global,
propertyName: 'say'
}; JavaScript 中属性访问有两种方式:点符号和中括号符号。
代码如下:
// Invokes the say method.
foo.say();
foo['say'](); 由于 say() 方法是标识符,它对应于 foo 对象的引用类型如下:
代码如下:
// Reference type.
var fooSayReference = {
base: foo,
propertyName: 'say'
}; 由于 say() 方法的 base 属性值为 foo 对象,因此其对应的 this 属性也将指向 foo 对象。
假设直接调用 say() 方法,其对应的引用类型如下:
代码如下:
// Reference type.
var sayReference = {
base: global,
propertyName: 'say'
}; 由于 say() 方法的 base 属性值为 global(通常为 window 对象),因此其对应的 this 属性也将指向 global。
函数调用以及非引用类型
函数上下文中 this 的值由函数调用者提供,并由当前调用表达式的形式决定。如果在调用括号 () 的左边存在引用类型的值,那么 this 的值就会设置为该引用类型值的 base 对象。所有其他情况(非引用类型),this 的值总是 null。然而,由于 null 对 this 而言无实际意义,因此会隐式转换为全局对象。
函数调用以及非引用类型
前文提到,当调用括号左侧为非引用类型时,this 的值会设置为 null,并最终隐式转换为全局对象。
现在定义一个匿名自执行函数,具体实现如下:
代码如下:
// Declares anonymous function
(function () {
alert(this); // null => global
})(); 由于括号 () 左边的匿名函数是非引用类型对象(既不是标识符也不属于属性访问),因此,this 的值设置为全局对象。
代码如下:
// Declares object.
var foo = {
bar: function () {
alert(this);
}
};
(foo.bar)(); // foo.
(foo.bar = foo.bar)(); // global?
(false || foo.bar)(); // global?
(foo.bar, foo.bar)(); // global 注意这四个表达式,只有第一个表达式 this 指向 foo 对象,而其他三个表达式执行时 this 指向 global。
这里产生疑问:为何同样是属性访问,最终 this 的值不是引用类型对象而是全局对象?
观察表达式二是赋值操作符(assignment operator),与表达式一的操作符不同,它会触发调用 GetValue 方法(参见 ECMAScript 规范 11.13.1 中的第三步)。最后返回的是一个函数对象(而非引用类型的值),这意味着 this 的值会设置为 null,最终变为全局对象。
第三和第四种情况同理——逗号操作符和 || 逻辑表达式都会触发调用 GetValue 方法,导致原先的引用类型值丢失,变为函数类型,因此 this 的值变成了全局对象。
引用类型以及this的null值
引用类型以及 this 的 null 值
对于前述情形,还有一种例外情况:当调用表达式左侧是引用类型的值,但 this 的值却是 null,最终变为全局对象(global object)。发生这种情况的条件是:引用类型值的 base 对象恰好为活跃对象(activation object)。
当内部子函数在父函数中被调用时就会发生这种情况,通过下面的示意代码介绍活跃对象:
代码如下:
// Declares foo function.
function foo() {
function bar() {
alert(this); // global
}
// The same as AO.bar().
bar();
} 由于活跃对象(activation object)总是会返回 this 值为——null(用伪代码来表示 AO.bar() 就相当于 null.bar()),然后,this 的值最终会由 null 转变为全局对象。
当函数调用包含在 with 语句的代码块中,并且 with 对象包含一个函数属性的时候,也会出现例外情况。with 语句会将该对象添加到作用域链的最前面,位于活跃对象之前。相应地,在引用类型的值(标识符或者属性访问)的情况下,base 对象就不再是活跃对象了,而是 with 语句的对象。另外,值得一提的是,它不仅仅只针对内部函数,全局函数也是如此,原因在于 with 对象掩盖了作用域链中更高层的对象(全局对象或者活跃对象)。
函数作为构造器被调用时this的值
函数作为构造器被调用时 this 的值
函数作为构造函数时,我们通过 new 操作符创建实例对象时,它会调用 Foo() 函数的内部 [[Construct]] 方法;在对象创建之后,会调用内部的 [[Call]] 方法,然后所有 Foo() 函数中 this 的值会设置为新创建的对象。
代码如下:
// Declares constructor
function Foo() {
// The new object.
alert(this);
this.x = 10;
}
var foo = new Foo();
foo.x = 23;
alert(foo.x); // 23 手动设置函数调用时this的值
手动设置函数调用时 this 的值
Function.prototype 原型上定义了两个方法,允许手动指定函数调用时 this 的值。这两个方法分别是:.apply() 和 .call()。这两个方法都接受第一个参数作为调用上下文中 this 的值,而这两个方法的区别是传递的参数:对于 .apply() 方法来说,第二个参数接受数组类型(或者是类数组的对象,比如 arguments),而 .call() 方法接受任意多的参数(通过逗号分隔);这两个方法只有第一个参数是必要的——this 的值。
通过示例代码介绍 call() 方法和 apply() 方法的使用:
代码如下:
var myObject = {};
var myFunction = function(param1, param2) {
//setviacall()'this'points to my Object when function is invoked
this.foo = param1;
this.bar = param2;
//logs Object{foo = 'foo', bar = 'bar'}
console.log(this);
};
// invokes function, set this value to myObject
myFunction.call(myObject, 'foo', 'bar');
// logs Object {foo = 'foo', bar = 'bar'}
console.log(myObject); call() 方法第一个参数是必要的 this 值,接着我们可以传递任意多个参数。接下来介绍 apply() 方法的使用。
代码如下:
var myObject = {};
var myFunction = function(param1, param2) {
//set via apply(), this points to my Object when function is invoked
this.foo=param1;
this.bar=param2;
// logs Object{foo='foo', bar='bar'}
console.log(this);
};
// invoke function, set this value
myFunction.apply(myObject, ['foo', 'bar']);
// logs Object {foo = 'foo', bar = 'bar'}
console.log(myObject); 通过与 call() 方法对比,我们发现 apply() 方法和 call() 方法没有太大的区别,只是方法签名不一样。
1.1.3 总结
总结
本文介绍了 JavaScript 中 this 的使用,更重要的是帮助我们能更好地理解 this 值在全局、函数、构造函数以及一些特例的情况中值的变化。
对于在函数上下文中 this 的值是函数调用者提供并且由当前调用表达式的形式而定的。如果在调用括号 () 的左边有引用类型的值,那么 this 的值就会设置为该引用类型值的 base 对象。所有其他情况下(非引用类型),this 的值总是 null。然而,由于 null 对于 this 来说没有任何意义,因此会隐式转换为全局对象。
对于特例情况,我们要记住赋值符、逗号操作符以及 || 逻辑表达式,会使 this 丢失原先的引用类型值,变成了函数类型,this 的值就变成了全局对象了。
参考
[1] http://dmitrysoshnikov.com/ecmascript/chapter-3-this/ 英文版
[2] http://blog.goddyzhao.me/post/11218727474/this 译文
[3] https://net.tutsplus.com/tutorials/javascript-ajax/fully-understanding-the-this-keyword/
D3.js 比例尺与坐标轴的配置详解
2026-11-05 16:54:46
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
