本文详细解析原生JavaScript中执行环境与作用域的核心概念。阐述执行环境(基于对象)与作用域(基于函数)的区别,介绍执行栈的工作机制、变量对象与活动对象的创建过程,以及作用域链在变量查找中的层级规则。通过代码示例说明全局与局部环境的交互及标识符解析顺序,帮助开发者深入理解JS底层运行机制。
执行环境与作用域的基本概念
首先需要明确,执行环境(Execution Context)与作用域(Scope)是两个截然不同的概念。
每次函数调用都会生成对应的作用域和执行环境。从本质上看,作用域是依附于函数的,而执行环境则是依附于对象的(例如全局执行环境对应 window 对象)。
具体而言,作用域决定了函数内部变量的访问权限,且不同调用场景下的访问规则可能不同。执行环境始终与 this 关键字的值相关联,它引用了当前正在执行代码的对象。每个执行环境都关联着一个变量对象(Variable Object),所有在该环境中定义的变量和函数都存储于此。尽管开发者编写的代码无法直接访问该对象,但 JavaScript 解析器在处理数据时会在后台自动使用它。
执行环境的运行机制
执行环境(Execution Context)
当 JavaScript 解释器开始初始化并执行代码时,默认首先进入全局执行环境。从这一刻起,每当有函数被调用,系统都会创建一个新的执行环境。
每个函数都拥有独立的执行环境。当执行流进入函数时,该函数的环境会被压入执行环境栈(Execution Stack)中。函数执行完毕后,栈会将该环境弹出,并将控制权交还给之前的执行环境。ECMAScript 正是利用这种栈机制来控制程序的执行流。
执行环境的创建过程可分为两个阶段:创建阶段和执行阶段。在创建阶段,解析器首先构建一个变量对象(Variable Object,在函数环境中也称为活动对象 Activation Object),其中包含定义在执行环境中的变量、函数声明以及参数。在此阶段,作用域链被初始化,this 的值也被最终确定。随后进入执行阶段,代码被逐行解释并执行。
Demo:
<script type="text/javascript">
function Fn1(){
function Fn2(){
alert(document.body.tagName);//BODY
//other code...
}
Fn2();
}
Fn1();
//code here
</script>

小结
当 JavaScript 代码被浏览器加载后,默认首先进入全局执行环境。若在全局环境中调用函数,程序流进入该函数内部,此时 JS 引擎会为该函数创建一个新的执行环境,并将其压入执行环境栈的顶部。浏览器始终执行栈顶部的执行环境;一旦执行完毕,该环境从栈顶弹出,随后执行其下方的执行环境。如此循环,直到所有局部环境弹出,最终回到全局执行环境。
此外,还需注意以下关键特性:
单线程:JavaScript 引擎是单线程运行的。
同步执行:代码按顺序同步执行。
唯一的全局执行环境:整个程序中只有一个全局执行环境。
局部执行环境的数量没有限制。
每次函数被调用,都会创建一个新的局部执行环境。即使是递归调用自身多次,也会为每次调用创建不同的局部执行环境。
作用域链与变量查找
作用域(Scope)
当代码在特定环境中执行时,会创建一个变量对象的作用域链(Scope Chain)。作用域链的主要作用是确保对执行环境中所有有权访问的变量和函数进行有序访问。
作用域链包含了执行环境栈中每个执行环境对应的变量对象。通过作用域链,可以确定变量的访问权限以及标识符的解析规则。
注意:全局执行环境的变量对象始终位于作用域链的末端。
在访问变量时,存在可见性问题:内层环境可以访问外层环境的变量和函数,但外层环境无法访问内层环境的变量和函数。更具体地说,当访问变量或调用函数时,JavaScript 引擎会根据规则将不同执行环境中的变量对象构建为一个链表。在查找变量时,引擎先从链表的第一个变量对象开始查找,若未找到则继续向后查找,直到搜索到全局执行环境的变量对象(即 window 对象)。这一机制构成了作用域链(Scope Chain)的概念。

作用域链图清晰展示了执行环境与作用域之间的一一对应关系,以及作用域之间通过链表结构形成的由上至下的层级关系。
Demo:
var color = "blue";
function changeColor(){
var anotherColor = "red";
function swapColors(){
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问color, anotherColor, 和 tempColor
}
// 这里可以访问color 和 anotherColor,但是不能访问 tempColor
swapColors();
}
changeColor();
// 这里只能访问color
console.log("Color is now " + color);
上述代码共包含三个执行环境:全局执行环境、changeColor() 的局部执行环境以及 swapColors() 的局部执行环境。
全局环境中包含一个变量 color 和一个函数 changeColor()。
changeColor() 函数的局部环境中包含一个 anotherColor 属性和一个 swapColors 函数。在 changeColor 函数内部,可以访问自身以及其外围(即全局环境)中的变量。
swapColors() 函数的局部环境中包含一个变量 tempColor。在该函数内部,可以访问其父执行环境(即 changeColor 和 window)中的所有变量,因为这两个环境都是它的父级执行环境。
上述代码的作用域链如下图所示:

从上图可以看出,内部环境可以通过作用域链访问所有外部环境,但外部环境无法访问内部环境中的任何变量和函数。
标识符解析(即搜索变量名或函数名)是一个沿着作用域链逐级搜索标识符的过程。搜索始终从作用域链的前端开始,然后逐级向后(指向全局执行环境)回溯,直到找到目标标识符为止。
执行环境与作用域的联系
执行环境与作用域的区别与联系
执行环境分为全局执行环境和局部执行环境,其中局部执行环境是在函数执行过程中创建的。
作用域链是基于执行环境的变量对象构建的,由所有执行环境的变量对象(对于函数而言是活动对象,因为在函数执行环境中,变量对象不能直接访问,此时由活动对象 Activation Object,缩写为 AO 扮演 Variable Object 的角色)共同组成。
当代码在某个环境中执行时,会创建该变量对象的作用域链。作用域链的用途是保证对执行环境中所有有权访问的变量和函数进行有序访问。作用域链的前端始终是当前执行代码所在环境的变量对象。
小练习
<script type="text/javascript">
(function(){
a= 5;
console.log(window.a);//undefined
var a = 1;//这里会发生变量声明提升
console.log(a);//1
})();
</script>
window.a 的值为 undefined,是因为 var a = 1; 发生了变量声明提升。其效果相当于以下代码:
<script type="text/javascript">
(function(){
var a;//a是局部变量
a = 5;//这里局部环境中有a,就不会找全局中的
console.log(window.a);//undefined
a = 1;//这里会发生变量声明提升
console.log(a);//1
})();
</script>
以上就是小编为大家带来的老生常谈原生JS执行环境与作用域全部内容了,希望大家多多支持本站~