在 JavaScript 的学习过程中,this 关键字往往是最令人困惑的机制之一。很多开发者分不清"作用域链"查找变量和 this 指向的区别。
本文将结合 V8 引擎的 执行上下文(Execution Context) 机制,深入剖析 this 的设计初衷、历史包袱,并重点讲解 箭头函数 是如何打破传统规则,回归词法作用域的。
一、 静态的作用域 vs 动态的 this
首先,我们需要区分两个核心概念:作用域(Scope) 和 this。
在 JavaScript 中,变量的查找遵循"词法作用域(Lexical Scope)"。这意味着变量的作用域是由代码声明的位置决定的,在编译阶段就已经确定。
- 作用域链(Outer) :当函数内部使用了一个既不是参数也不是局部变量的"自由变量"时,引擎会沿着作用域链(
outer)向外查找。这个outer指向的是定义该函数时的词法环境。 this:与作用域不同,this是在函数执行阶段(运行时)决定的。它是执行上下文中的一个属性。
结论 :在普通函数中,谁调用了这个方法,this 就指向谁;这与函数在哪里定义无关,只与函数如何被调用有关。
二、 核心机制:执行上下文
要真正理解 this,必须看向底层。当 JavaScript 执行一段代码时,会创建执行上下文。根据示意图,一个执行上下文包含以下四个部分:
- 变量环境 (Variable Environment) :存放
var声明的变量。 - 词法环境 (Lexical Environment) :存放
let、const变量。 - Outer:指向外部作用域,用于变量查找(作用域链)。
- ThisBinding :即我们讨论的
this。
这就解释了为什么 this 是动态的------因为每次函数调用都会创建一个新的执行上下文,而 this 是在这个上下文中被绑定的。
三、 为什么要有 this?(设计哲学与缺陷)
在早期的 JavaScript 设计中,虽然它是一门函数式语言,但为了迎合当时的 Java 潮流,需要模拟"面向对象(OOP)"的特性。在对象的方法内,必须有一个机制能操作对象自身的属性,于是引入了 this。
然而,这也带来了一个著名的设计缺陷:普通函数的 this 指向。
当一个函数被"裸调用"(不作为对象方法,也不使用 new)时:
- 非严格模式 :
this默认指向全局对象(浏览器中是window)。这容易导致全局变量污染。 - 严格模式 :为了修复这个问题,
'use strict'下普通函数的this会被绑定为undefined。
四、 this 绑定的常规规则
根据调用方式的不同,this 的绑定可以归纳为以下几种常规场景:
- 默认绑定 :普通函数运行,
this指向全局对象或undefined。 - 隐式绑定 :
obj.method(),this指向obj。 - 显式绑定 :
call/apply/bind,this指向指定对象。 - 构造函数绑定 :
new调用,this指向新实例。 - 事件绑定 :DOM 事件中,
this指向绑定事件的元素。
五、 箭头函数:打破规则的特例
ES6 引入的**箭头函数(Arrow Function)**是 this 机制中最大的特例,它彻底改变了 this 的查找规则。
1. 本质区别:回归词法作用域
在普通函数中,this 是执行上下文中的一个特殊属性(ThisBinding),由调用方式决定。
而在箭头函数中,它本身没有自己的 this 绑定。
那么,箭头函数里的 this 是什么?
文档中明确指出:箭头函数中,this 是从定义时的外层词法作用域继承的。
这意味着,箭头函数处理 this 的方式,和处理普通变量(如 myName)是一样的:
- 查找 :当在箭头函数中使用
this时,引擎会去查找当前执行上下文。 - 向上追溯 :发现当前(箭头函数)上下文不存在
this绑定,于是沿着**作用域链(Outer)**向外层查找。 - 确定 :它会使用定义该箭头函数时 所在环境的
this。
2. 解决了什么痛点?
在 ES6 之前,我们在回调函数中使用 this 非常痛苦,经常面临"隐式丢失"的问题:
JavaScript
var myObj = {
name: "极客时间",
showThis: function(){
// 如果这里使用 setTimeout(function() { ... }, 1000)
// 普通函数的 this 会指向 window (非严格模式),导致无法获取 this.name
// 传统的"黑魔法":
var self = this; // 显式保存外层的 this
// 现在的箭头函数:
setTimeout(() => {
// 这里的 this 继承自 showThis 函数的执行上下文
console.log(this.name);
}, 1000);
}
}
myObj.showThis();
由于箭头函数的 this 是静态 的(由定义位置决定,类似于词法作用域),它不会因为函数调用的方式(如被 setTimeout 调用)而改变指向。这让代码更加符合直觉。
3. 不可被修改
正因为箭头函数的 this 是基于词法作用域链查找的,所以它无法 通过 call、apply 或 bind 来改变。
即使你写了 arrowFunc.call(obj),引擎也会忽略这个 obj,仍然按照词法作用域去寻找 this。
六、 总结
理解 this 的关键在于区分两种模式:
- 动态模式(普通函数) :忘掉"函数在哪里定义",牢记"函数是如何被执行的"。执行上下文在运行时决定
this的指向。 - 静态模式(箭头函数) :忘掉"函数怎么被调用",牢记"函数在哪里定义"。它利用**词法作用域链(Outer)**机制,继承外层的
this。
JS 的这一设计演变,展示了语言从"模仿 Java OOP"到"回归函数式与词法作用域"的进化过程。希望通过本文,你能从底层的执行上下文视角,彻底掌握这一核心机制。