JavaScript中this指向详解:从决策树到执行环境

作者:拾光

时间:2026-10-30

浏览:658

本文深入解析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 的指向逻辑,下面通过一张决策图来进行解析:

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 指向的图解分析如下所示:

JavaScript 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 指向的图解分析如下所示:

JavaScript 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 函数的运行机制大有裨益。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章