四、作用域与作用域链

简介: 作用域与作用域链本应该是一个非常简单的概念。可是在近两年多的留言中,我发现这些概念反而成为了大多数人想不明白的点,而感到困惑的原因在于,别的文章里,常常会提到词法作用域,词法分析等概念,到底是什么东西?好像跟我说的有一点不一样,但又不知道哪里不对?为了避免接下来更多的同学造成同样的困扰,我写了一篇名为(v8引擎是如何工作的)文章,为大家分析JS的工作原理。后续会整理进入基础进阶系列文章。大家可以随时阅读。

微信图片_20220510132832.jpg


作用域与作用域链本应该是一个非常简单的概念。可是在近两年多的留言中,我发现这些概念反而成为了大多数人想不明白的点,而感到困惑的原因在于,别的文章里,常常会提到词法作用域,词法分析等概念,到底是什么东西?好像跟我说的有一点不一样,但又不知道哪里不对?


为了避免接下来更多的同学造成同样的困扰,我写了一篇名为(v8引擎是如何工作的)文章,为大家分析JS的工作原理。后续会整理进入基础进阶系列文章。大家可以随时阅读。


词法环境(Lexical Environments)


官方规范对词法环境的说明是:词法环境(Lexical Environments)是一种规范类型,用于根据ECMAScript代码的词法嵌套结构来定义标识符与特定变量和函数的关联。


官方就是爱把简单的东西复杂化,专业!


通俗来说,词法环境就是一套约定好的规则。我们写代码,应该按照这个规则来。JS引擎对JS源码进行词法分析,也是按照这个规则来。


因此,词法环境,在我们写代码的时候就已经确定了。明白了这一点,对于区分词法环境与执行上下文有非常关键的意义。


词法环境,其实就是作用域,有的人叫词法作用域等等


得出结论:


一、在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理JS引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。


这里的标识符,指的是变量名或者函数名


二、JavaScript中有全局作用域与函数作用域(因为eval我们平时开发中几乎不会用到它,这里不讨论)。


现在,大家知道词法环境,作用域和执行上下文的区别了吗?


更多的关于JS引擎是如何处理JS代码的,大家可以参考(V8引擎是如何工作的)一文


除此之外,一个词法环境,由环境记录(Environment Records)与一个外部指向outer组成。


环境记录如果非要细分的话,又可以分为函数环境记录和声明性的环境记录。而环境记录对应的,其实就是变量对象,outer对应的,就是上文中出现过,但是没有引起重视的scopeChain


作用域链


首先我们要明确的一点是,作用域和作用域链是不同的。


作用域是一套规则。


而作用域链则是在代码执行过程中,会动态变化的一条索引路径。


在理解之前,需要回顾一下上一篇文章我们分析的执行上下文的生命周期,如下图。


微信图片_20220510132836.png


我们知道函数在调用激活时,会开始创建对应的执行上下文,在执行上下文生成的过程中,变量对象,作用域链,以及this的值会分别被确定。之前一篇文章我们详细说明了变量对象,而这里,我们将详细说明作用域链。


作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。


为了帮助大家理解作用域链,我我们先结合一个例子,以及相应的图示来说明。


var a = 20;
function test() {
  var b = a + 10;
  function innerTest() {
    var c = 10;
    return b + c;
  }
  return innerTest();
}
test();


在上面的例子中,全局,函数test,函数innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test), VO(innerTest)。而innerTest的作用域链,则同时包含了这三个变量对象,所以innerTest的执行上下文可如下表示。


innerTestEC = {
  VO: {...},  // 变量对象
  scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链
}


我们这里使用一个数组来模拟作用域链,数组的第一项scopeChain[0]为作用域链的最前端,而数组的最后一项,为作用域链的最末端,所有的最末端都为全局变量对象。


很多人会误解为当前作用域与上层作用域为包含关系,但其实并不是。以最前端为起点,最末端为终点的单方向通道我认为是更加贴切的形容。如图。


微信图片_20220510132839.jpg


注意,因为变量对象在执行上下文进入执行阶段时,就变成了活动对象,这一点在上一篇文章中已经讲过,因此图中使用了AO来表示。Active Object


作用域链是由一系列变量对象组成,我们可以在这个单向通道中,查询变量对象中的标识符,这样,就能访问到上一层作用域中的变量。


最后,用一个问题,验证一下大家对作用域和作用域链以及变量对象的理解:


foo的执行结果是什么?


var a = 20;
function foo() {
  if (!a) {
    a = 100;
  }
  var a = 10;
  return a;
}
console.log(foo());
相关文章
|
7月前
|
JavaScript 前端开发
作用域链的理解
作用域链的理解
60 0
|
2月前
|
JavaScript 前端开发
作用域和作用域链及预解析
作用域和作用域链及预解析
24 4
|
3月前
|
Java
作用域
作用域
21 2
|
3月前
C 作用域详解
在 C 语言中,作用域决定了变量和函数的可见性和生命周期,包括块作用域、函数作用域、文件作用域和全局作用域。块作用域内的变量仅在块内有效,函数作用域内的变量在整个函数内有效,文件作用域内的全局变量和函数在整个文件内有效,而全局作用域内的变量和函数在整个程序运行期间有效。作用域的优先级遵循局部变量优先的原则,局部变量会遮蔽同名的全局变量。变量的生命周期分为局部变量(函数调用时创建和销毁)、全局变量(程序开始时创建和结束时销毁)以及静态变量(整个程序期间有效)。理解作用域有助于避免命名冲突和错误,提高代码的可读性和可维护性。
|
7月前
|
自然语言处理 JavaScript 前端开发
深入理解作用域、作用域链和闭包
在 JavaScript 中,作用域是指变量在代码中可访问的范围。理解 JavaScript 的作用域和作用域链对于编写高质量的代码至关重要。本文将详细介绍 JavaScript 中的词法作用域、作用域链和闭包的概念,并探讨它们在实际开发中的应用场景。
|
7月前
|
自然语言处理 JavaScript 前端开发
对作用域链的理解
对作用域链的理解
60 0
|
设计模式 自然语言处理 JavaScript
一篇文章帮你真正理解javascsript作用域闭包
一篇文章帮你真正理解javascsript作用域闭包
90 0
|
消息中间件 存储 自然语言处理
兄台: 作用域、执行上下文了解一下
• 作用域(Scopes) • 词法环境(Lexical environments) • 作用域链 • 执行上下文 • 调用栈
108 0
|
存储 缓存 JavaScript
深入理解作用域和闭包(下)
深入理解作用域和闭包(下)
深入理解作用域和闭包(下)
|
存储 JavaScript 前端开发
深入理解作用域和闭包(上)
深入理解作用域和闭包(上)
深入理解作用域和闭包(上)