本文深入解析JavaScript中this关键字的指向逻辑。通过“this决策树”图解对象方法、直接函数、构造函数及apply/call四种调用场景下的this绑定差异,并结合IBM文档阐述执行环境创建过程,帮助开发者彻底掌握this的动态绑定机制。
JavaScript this 的动态绑定特性
JavaScript 作为一种支持函数式编程、闭包及基于原型继承的高级脚本语言,其入门门槛看似较低,但深入使用后会发现许多概念颇具挑战,其中 this 关键字便是典型代表。在不同调用场景下,this 会指向不同的对象。业界普遍认为,只有透彻理解 this 的工作机制,才算真正掌握了 JavaScript。与 Java、C# 等主流面向对象语言在编译期将 this 明确绑定为当前对象不同,JavaScript 中的 this 是在运行期动态绑定的,这种运行时绑定的特性赋予了 this 多重含义。
JavaScript 函数的四种调用方式
得益于运行期绑定的特性,JavaScript 中的 this 可以指向全局对象、当前对象或任意对象,具体指向完全取决于函数的调用方式。JavaScript 主要包含以下几种函数调用模式:
作为对象的方法进行调用;
作为普通函数直接调用;
作为构造函数进行调用;
通过 apply 或 call 方法强制指定上下文进行调用。
俗话说,文字不如表格,表格不如图表。为了更直观地理解 JavaScript 中 this 的指向逻辑,下面通过一张决策图来进行解析:

基于决策树的 this 指向判断
上图被称为“JavaScript this 决策树”(适用于非严格模式)。接下来通过具体示例演示如何利用该决策树判断 this 的指向:
var point = {
x: 0,
y: 0,
moveTo: function(x, y) {
this.x = this.x + x;
this.y = this.y + y;
}
};
// 决策树解释:point.moveTo(1,1)函数不是new进行调用,进入否决策,
// 是用dot(.)进行调用,则指向.moveTo之前的调用对象,即point
point.moveTo(1,1); // this 绑定到当前对象,即point对象
首先考察 point.moveTo 函数的调用方式,是否使用了 new 关键字?显然没有,因此进入“否”分支,接着判断该函数是否通过点号(.)进行调用?
point.moveTo 函数确实是通过点号(.)调用的,因此进入“是”分支。此时,this 指向点号之前的对象,即 point 对象本身。
point.moveTo 函数中 this 指向的图解分析如下所示:

再看另一个示例代码:
function func(x) {
this.x = x;
}
func(5); // this是全局对象window,x为全局变量
//决策树解析:func()函数是用new进行调用的么?为否,进入func()函数是用dot进行调用的么?为否,则 this指向全局对象window
x; // x => 5
func() 函数在“JavaScript this 决策树”中的判定流程如下:
func(5) 的调用是否使用了 new 关键字?显然没有,进入“否”分支,接着判断该函数是否通过点号(.)进行调用?
func(5) 并非通过点号调用,因此进入“否”分支。此时 this 指向全局变量 window,故 this.x 实际上等同于 window.x。
func 函数中 this 指向的图解分析如下所示:

复杂调用场景下的 this 分析
针对直接函数调用的复杂情况,下面看一个更深入的例子:
var point = {
x: 0,
y: 0,
moveTo: function(x, y) {
// 内部函数
var moveX = function(x) {
this.x = x; // this 指向什么?window
};
// 内部函数
var moveY = function(y) {
this.y = y; //this 指向什么?window
};
moveX(x);
moveY(y);
}
};
point.moveTo(1,1);
point.x; // =>0
point.y; // =>0
x; // =>1
y; // =>1
point.moveTo(1,1) 函数内部实际调用了 moveX() 和 moveY() 函数。其中,moveX() 函数内部的 this 在“JavaScript this 决策树”中的判定流程如下:
moveX(1) 的调用是否使用了 new 关键字?显然没有,进入“否”分支,接着判断该函数是否通过点号(.)进行调用?
moveX(1) 并非通过点号调用,因此进入“否”分支。此时 this 指向全局变量 window,故 this.x 实际上等同于 window.x。
构造函数调用中的 this 差异
接下来查看作为构造函数调用的示例:
function Point(x,y) {
this.x = x; // this ?
this.y = y; // this ?
}
var np = new Point(1,1);
np.x; // 1
var p = Point(2,2);
p.x; // error, p是一个空对象undefined
window.x; // 2
在语句 var np=new Point(1,1) 中,Point(1,1) 函数的 this 在“JavaScript this 决策树”中的判定流程如下:
var np=new Point(1,1) 的调用是否使用了 new 关键字?显然是的,因此进入“是”分支,此时 this 指向新创建的实例 np;
因此 this.x=1 等价于 np.x=1;
再看 Point(2,2) 函数在语句 var p= Point(2,2) 中的 this 在“JavaScript this 决策树”中的判定流程:
var p= Point(2,2) 的调用是否使用了 new 关键字?显然没有,进入“否”分支,接着判断该函数是否通过点号(.)进行调用?
Point(2,2) 并非通过点号调用,判定为否,进入“否”分支。此时 this 指向全局变量 window,故 this.x 实际上等同于 window.x;
因此 this.x=2 等价于 window.x=2。
apply 与 call 对 this 的重定向
最后查看通过 call 和 apply 进行调用的示例:
function Point(x, y) {
this.x = x;
this.y = y;
this.moveTo = function(x, y) {
this.x = x;
this.y = y;
}
}
var p1 = new Point(0, 0);
var p2 = {x: 0, y: 0};
p1.moveTo.apply(p2, [10, 10]); // apply 实际上为 p2.moveTo(10,10)
p2.x // 10
p1.moveTo.apply(p2,[10,10]) 函数在“JavaScript this 决策树”中的判定流程如下:
我们知道,apply 和 call 方法功能强大,允许切换函数执行的上下文环境(context),即改变 this 绑定的对象。p1.moveTo.apply(p2,[10,10]) 在效果上等同于 p2.moveTo(10,10)。那么 p2.moveTo(10,10) 可以解释为:
p2.moveTo(10,10) 的调用是否使用了 new 关键字?显然没有,进入“否”分支,接着判断该函数是否通过点号(.)进行调用?
p2.moveTo(10,10) 是通过点号调用的,因此进入“是”分支。此时 this 指向点号之前的对象 p2,所以 p2.x=10。
函数执行环境(ExecutionContext)解析
关于 JavaScript 函数执行环境的过程,IBM developerworks 文档库中的一段描述非常精辟,摘抄如下:
“JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因。一个函数被执行时,会创建一个执行环境(ExecutionContext),函数的所有的行为均发生在此执行环境中,构建该执行环境时,JavaScript 首先会创建 arguments变量,其中包含调用函数时传入的参数。接下来创建作用域链。然后初始化变量,首先初始化函数的形参表,值为 arguments变量中对应的值,如果 arguments变量中没有对应值,则该形参初始化为 undefined。如果该函数中含有内部函数,则初始化这些内部函数。如果没有,继续初始化该函数内定义的局部变量,需要注意的是此时这些变量初始化为 undefined,其赋值操作在执行环境(ExecutionContext)创建成功后,函数执行时才会执行,这点对于我们理解 JavaScript 中的变量作用域非常重要,鉴于篇幅,我们先不在这里讨论这个话题。最后为 this变量赋值,如前所述,会根据函数调用方式的不同,赋给 this全局对象,当前对象等。至此函数的执行环境(ExecutionContext)创建成功,函数开始逐行执行,所需变量均从之前构建好的执行环境(ExecutionContext)中读取。”
理解这段话对于深入理解 JavaScript 函数的运行机制大有裨益。