JavaScript 作用域链与执行环境深度解析

作者:青柠

时间:2026-10-22

浏览:421

本文深入解析 JavaScript 中的作用域、作用域链及执行环境栈机制。详细阐述函数级作用域特性、标识符解析规则、执行上下文的生命周期管理,以及 this 对象的判定逻辑。通过代码示例说明变量查找顺序、遮蔽效应及性能优化策略,帮助开发者理清概念混淆。

文章目录

引言与核心概念梳理

前言:这是笔者学习之后自己的理解与整理。如果有错误或者疑问的地方,请大家指正,我会持续更新!

在 JavaScript 中,作用域、作用域链、执行环境、执行环境栈以及 this 的概念至关重要,笔者曾经常将这些概念混淆,因此在此进行梳理和澄清。

  • 局部作用域指的是函数内部的区域,而全局作用域则对应于 window 对象。
  • 作用域链由函数声明时的位置决定。在解析标识符时,引擎会先查找当前作用域,若未找到则向外层查找,直至全局作用域。这一查找顺序是固定的,与函数实际调用的位置无关。
  • 执行环境是指函数可访问的数据和变量的集合,即该函数作用域链上包含的所有数据和变量。
  • 执行环境栈依据代码执行顺序,将各执行环境以栈的形式逐层访问。当某个执行环境使用完毕后会被移除并丢弃。需要注意的是,如果当前执行环境(包含当前作用域链的数据和变量)中找不到变量,则直接判定为找不到,不会回溯到之前的执行环境查找,这与作用域链的机制不同。
  • this 是一个对象,其值取决于执行者是谁,即由谁执行就指向谁。关于 this 的概念理解尚不完全透彻,此处描述较为宽泛,后续可能会进行修正。

JavaScript 作用域机制

作用域

JavaScript 没有块级作用域的概念,仅具备函数级作用域:变量在声明它们的函数体及其子函数内可见。

作用域定义了变量和函数的可访问范围,控制着它们的可见性与生命周期。在 JavaScript 中,变量的作用域主要分为全局作用域和局部作用域。

未在函数内部声明的变量,或声明时未使用 var 关键字的变量,均为全局变量,拥有全局作用域。


 <script type="text/javascript">
 function test1(){
  a = 1;//全局变量,只有在当前函数运行时,才有效
  }
 test1();
 console.log(a);//1 注意test1函数必须运行,不然找不到a
 </script>

全局变量可以作为 window 对象的属性来访问,两者在本质上是相同的。


<script type="text/javascript"> 
 var b = 1;//全局变量
 console.log(b === window.b);//true 全局变量可以当做window对象的属性用,他们是一样的;
</script>

window 对象的所有属性都拥有全局作用域,可以在代码的任何位置进行访问。

在函数内部声明的变量称为局部变量,仅能在函数体内使用。即使函数的参数未使用 var 声明,它们仍然是局部变量。


 <script type="text/javascript"> 
   var c = 1;//全局变量
//   console.log(d);//ReferenceError: d is not defined 引用错误,当前作用域就是最外层作用域,依然找不到d
   function test2(d){
    console.log(c);//1 全局变量,哪都可以访问;(先找当前作用域,找不到,就向外层作用域找,直到window最外层,找到了)
    console.log(d);//3 形参是局部变量,只有当前作用域下可以访问
   }
   test2(3);
  </script>

作用域链与变量查找

作用域链

作用域链取决于函数被声明时的位置。解析标识符时,引擎先从当前作用域开始查找;若在当前作用域中未找到,则会在外层嵌套的作用域中继续查找,直到找到该变量或抵达最外层(即全局作用域)。这一查找路径在函数声明时已确定,与函数运行时的位置无关。


<script type="text/javascript">
   var a = 1;
   var b = 2;
   var c = 3;
   var d = 4;
   function inner(d) {//它的作用域链是inner---全局
    var c = 8;
    console.log(a);//1 当前作用域找不到a,去全局作用域找到了a=1
    console.log(b);//2 当前作用域找不到b,去全局作用域找到了b=2
    console.log(c);//8 当前作用域找到了c=8
    console.log(d);//7 当前作用域找到了d=7,形参也是局部作用域
   // console.log(e);//ReferenceError: e is not defined 引用错误,找不到e, 它的作用域链是inner---全局
    console.log(a+b+c+d);//18
   }
   function outter(e) {
    var a = 5;//inner()的作用域链是inner---全局,所以这个a相当于无效
    var b = 6;//inner()的作用域链是inner---全局,所以这个a相当于无效
    inner(7);
   }
   outter(999);//这个999无效,里面的e根本找不到
</script>

在多层嵌套作用域中可以定义同名的标识符,这被称为“遮蔽效应”,即内部标识符会遮蔽外部同名标识符。

通过 window.a 这种技术可以访问那些被同名变量遮蔽的全局变量。但如果非全局变量被遮蔽,则无论如何都无法被访问到。


<script type="text/javascript">
   var a = 'Lily';
   var b = 'Lucy';
   function outer() {
    var b = 'Jesica';
    var c = 'Susan';
    function inner(c) {
     console.log(a);//Lily 
     console.log(window.b);//Lucy
     console.log(b);//Jesica
     console.log(c);//Jenifer
    }
    inner('Jenifer');
   }
   outer();
</script>

执行环境与执行环境栈

执行环境

执行环境(execution context),也称为执行上下文。每个执行环境都关联一个变量对象(variable object),用于保存函数可访问的所有变量和数据(即函数作用域链上的所有数据和变量)。代码无法直接访问该对象,它是供引擎内部使用的。

执行环境栈:当执行进入一个函数时,该函数的执行环境会被推入栈中。函数执行完毕后,栈会将其执行环境移除,其中的变量和数据会被标记清除并等待垃圾回收,随后将控制权交还给之前的执行环境。JavaScript 程序的执行正是由这一机制控制的。

需要注意的是,如果当前执行环境(存放当前作用域链里的数据和变量)找不到变量,那就是找不到了,不会往之前的那个执行环境查找,和作用域链是不一样的。

代码的执行顺序并非完全按行执行,而是与函数的调用顺序密切相关。

  • 代码进入全局执行环境,全局执行环境被放入环境栈。
  • 当执行到一个函数时,该函数的执行环境会被推入到环境栈的顶端,之前的执行环境则向后移。
  • 全局执行环境最先进入,因此一直位于栈底,这与栈的概念类似。
  • 函数执行完毕后,其执行环境会从作用域链顶端移除,其中保存的数据和函数会被标记清除,等待垃圾回收。
  • 控制权交还给之前的执行环境,继续往下执行。
  • 当页面关闭时,全局执行环境才会被销毁。

<script type="text/javascript">
 var a = 1;
 var b = 2;
 var c = 3;
 var d = 4;
 function inner(d) {//它的作用域链是inner---全局
  var c = 8;
  console.log(a);//1 当前作用域找不到a,去全局作用域找到了a=1
  console.log(b);//2 当前作用域找不到b,去全局作用域找到了b=2
  console.log(c);//8 当前作用域找到了c=8
  console.log(d);//7 当前作用域找到了d=7,形参也是局部作用域
 // console.log(e);//ReferenceError: e is not defined 引用错误,找不到e, 它的作用域链是inner---全局
  console.log(a+b+c+d);//18
 }
 function outter(e) {
  var a = 5;//inner()的作用域链是inner---全局,所以这个a相当于无效
  var b = 6;//inner()的作用域链是inner---全局,所以这个a相当于无效
  inner(7);
 }
 outter(999);//这个999无效,里面的e根本找不到
</script>

以上代码的执行顺序:

代码执行进入全局执行环境,并对全局执行环境中的代码进入声明提升; 执行第2行,赋值a=1; 然后第3行赋值b=2; 然后第4行赋值c=3; 然后第5行赋值d=4; 执行第20行,调用outer(999)函数,然后进入outer(999)函数执行环境,声明提升,并将实参999传给形参e;现在环境栈中有两个执行环境,outer(999)是当前执行环境; 执行第16行,赋值a=5; 然后第17行赋值b=6; 执行第18行,调用inner(7)函数,然后进入inner(7)函数执行环境,声明提升,并将实参7传给形参d; 执行第7行,赋值c=8; 然后运算并输出;

性能优化建议

代码优化

由于在作用域链上查找变量需要消耗性能,我们应该尽快找到变量。因此在函数多层嵌套时,应尽可能使用函数内部的局部变量。

在函数内部使用全局变量属于跨作用域操作。如果某个跨作用域的值在函数内部被多次使用,应将其存储到局部变量中,这样可以提高性能。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章