你不知道的JS系列【1】- 什么是作用域
几乎所有的编程语言都能够储存变量,并且能在之后对这个变量值进行访问或修改,正是储存和访问变量的能力将状态带给了程序,那么,这些变量储存在哪里呢?程序需要时又是如何找到他们?这些问题说明需要一套设计良好的规则来储存变量,并且之后可以方便的找到这些变量,这套规则被称为作用域。
1、了解编译原理
尽管将JS归类为“动态”或“解释执行”脚本语言,但事实上它是一门编译语言。但是与传统编译语言不同的是,它不是提前编译的,编译结果也不能在分布式系统中进行移植。JS引擎进行编译的步骤与传统的语言非常相似,程序中一段源代码在执行之前会经历三个步骤,统称为“编译”。
- 分词/词法分析
这个过程会将由字符组成的字符串分解成有意义的代码块,这些代码块被称为词法单元。例如,考虑程序
var a = 2;。这段程序通常会被分解成 为下面这些词法单元:var、a、=、2 、;。
- 解析/语法分析
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。
var a = 2;的抽象语法树中可能会有一个叫作VariableDeclaration的顶级节点,接下来是一个叫作 Identifier(它的值是 a)的子节点,以及一个叫作 AssignmentExpression 的子节点。AssignmentExpression 节点有一个叫作 NumericLiteral(它的值是 2)的子节点。
- 代码生成
AST转换为可执行代码的过程称被称为代码生成,简单来说就是有某种方法可以将
var a = 2;的AST转化为一组机器指 令,用来创建一个叫作a的变量(包括分配内存等),并将一个值储存在a中。
编译流程如下图所示:

JS引擎比传统的编译语言编译器复杂很多,在语法分析和代码生成阶段有特定的步骤来对性能进行优化,大部分情况下编译发生在代码之前的前几微秒,在讨论作用域背后,js引擎用了各种办法来保证性能最佳。
Tips:我们平时在写JS代码的时候,一个语句结尾要加分号(;),便于JS编译器编译。
2、理解作用域
我们先了解JS编译过程中几个名词,JS引擎,编译器,作用域。
2.1.名词介绍
- JS引擎:从头到尾负责整个JS程序编译过程。
- 编译器:负责语法分析及代码生成等。
- JS引擎:负责收集并维护由所有声明的标识符(变量)组成的一系列查 询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
2.2.变量赋值
对于var a=2;这段代码,我们认为这就是申明一个为变量a且初始值为2,实际上,JS引擎认为这里有两个完全不同的申明,一个由编译器在编译时处理,另一个则由引擎在运行时处理。
处理过程分为两步:
1.遇到var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为a。
2.接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理a = 2这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作a的变量。如果是,引擎就会使用这个变量;如果否,引擎会继续查找该变量。
如果引擎最终找到a变量,就会将2赋值给它。否则就抛出异常。
Tips:声明提前(hoist)-JS引擎在创建变量时,会将该变量提升到当前作用域的最前面。
总结:变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。
2.3.LHS查询&RHS查询
编译器在编译过程中的第二步生成了代码,引擎在执行时,会通过查找变量a来判断它是否已经声明过。当变量出现在赋值操作的左侧时进行LHS查询,当变量出现在右侧时进行RHS查询。
console.log(a); //对a的引用时RHS引用,这里没有对a赋予任何值,需要查找a的值。
a=2; //对a的引用是LHS引用,因为这里不关心a的值等于多少,只想为 =2 这个赋值操作找到一个目标(变量a);
LHS和RHS的含义是“赋值操作的左侧或右侧”并不一定意味着就是“= 赋值操作符的左侧或右侧”。赋值操作还有其他几种形式,因此在概念上最好将其理解为“赋值操作的目标是谁(LHS)”以及“去找到XX变量的值,谁是赋值操作的源头(RHS)”。
3、作用域嵌套
作用域是根据名称查找变量的一套规则。实际情况中,通常需要同时顾及几个作用域。 当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此,在当前作用 域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量, 或抵达最外层的作用域(也就是全局作用域)为止。
参考以下代码:
var name='peer';
function sayHello(){
alert('hello '+ name)
}
sayHello();
// 对name的RHS引用无法在函数sayHello完成,但是可以在上一级作用域中完成。
把作用域比喻成一个建筑如下图所示:

LHS和RHS引用都会在当前楼层进行查找,如果没有找到,就会坐电梯前往上一层楼,如果还是没有找到就继续向上,以此类推。一旦抵达顶层(全局作用域),可能找到了你所需的变量,也可能没找到,但无论如何查找过程都将停止。
4、总结
作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。LHS和RHS查询都会在当前执行作用域中开始,如果有需要就会向上级作用域继续查找目标标识符,这样每次上升一级作用域,最后抵达全局作用域,无论找到或没找到都将停止。不成功的RHS 引用会导致抛出ReferenceError异常。不成功的LHS引用会导致自动隐式地创建一个全局变量(非严格模式下),掌握这些基本作用域知识能使我们更深入理解JS引擎的编译过程来编写更高性能的代码。
参考资料:
《你不知道的JavaScript》
你不知道的JS系列【1】- 什么是作用域的更多相关文章
- 你不知道的 JS (系列丛书) - 第二版
你不知道的 JS (系列丛书) - 第二版 You Don't Know JS (book series) - 2nd Edition https://github.com/learning-js-b ...
- 你不知道的JS之作用域和闭包 附录
原文:你不知道的js系列 A 动态作用域 动态作用域 是和 JavaScript中的词法作用域 对立的概念. 动态作用域和 JavaScript 中的另外一个机制 (this)很相似. 词法作用域是 ...
- 你不知道的JS之作用域和闭包(五)作用域闭包
原文:你不知道的js系列 一个简单粗暴的定义 闭包就是即使一个函数在它所在的词法作用域外部被执行,这个函数依然可以访问这个作用域. 比如: function foo() { var a = 2; fu ...
- 你不知道的JS之作用域和闭包(四)(声明)提升
原文:你不知道的js系列 先有鸡还是先有蛋? 如下代码: a = 2; var a; console.log( a ); 很多开发者可能会认为结果会输出 undefined,因为 var a 在 a ...
- 你不知道的JS之作用域和闭包(三)函数 vs. 块级作用域
原文:你不知道的js系列 在第(二)节中提到的,标识符在作用域中声明,这些作用域就像是一个容器,一个嵌套一个,这个嵌套关系是在代码编写时定义的. 那么到底是什么产生了一个新的作用域,只有函数能做到 ...
- 你不知道的JS之作用域和闭包(二)词法作用域
原文:你不知道的js系列 词法作用域(Lexical Scope) Lex time 一个标准的编译器的第一个阶段就是分词(token化) 词法作用域就是在词法分析时定义的作用域.换句话说,词法作用域 ...
- 你不知道的JS之作用域和闭包(一)什么是作用域?
原文:你不知道的js系列 什么是作用域(Scope)? 作用域 是这样一组规则——它定义了如何存放变量,以及程序如何找到之前定义的变量. 编译器原理 JavaScript 通常被归类为动态语言或者解释 ...
- 你不知道的JS之 this 和对象原型(一)this 是什么
原文:你不知道的js系列 JavaScript 的 this 机制并没有那么复杂 为什么会有 this? 在如何使用 this 之前,我们要搞清楚一个问题,为什么要使用 this. 下面的代码尝试去 ...
- 翻译连载 | 第 10 章:异步的函数式(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...
随机推荐
- 零基础转行web前端,要学习多久?需要掌握些什么?
web前端开发技术人才越来越吃香,而且web前端领域划分越来越细,对技术的需求越来越高,想学习web前端的人也是越来越多.那么,如何学习web前端知识?从哪开始?转型成为web前端工程师需要学些什么? ...
- Python之类的特殊成员方法
类的特殊成员方法 1.__doc__ :打印类的描述信息 class Foo: """ 描述类信息,这是用于看片的神奇 """ def fu ...
- [CODEVS6333] (数据加强)特种部队
题目描述 Description 某特种部队接到一个任务,需要潜入一个仓库.该部队士兵分为两路,第一路士兵已经在正面牵制住了敌人,第二路士兵正在悄悄地从后方秘密潜入敌人的仓库.当他们到达仓库时候,发现 ...
- CentOS6.5下搭建文件共享服务(Samba)
Samba服务: 本内容为samba服务学习者提供参考 案例描述: 某公司的管理员需要搭建SAMBA服务器,IP地址及允许的访问网段自定义.SAMBA服务器的安全级别为user级,所在工作组为WORK ...
- jmeter-定时器使用
在一般性能测试过程中,往往在前一个请求之后等待一段时间再执行下一个请求,这时会用到定时器. 以下列举常用的3中: 1.固定定时器: 2.
- [Tarjan系列] Tarjan算法与有向图的SCC
前面的文章介绍了如何用Tarjan算法计算无向图中的e-DCC和v-DCC以及如何缩点. 本篇文章资料参考:李煜东<算法竞赛进阶指南> 这一篇我们讲如何用Tarjan算法求有向图的SCC( ...
- 冷知识: 不会出现OutOfMemoryError的内存区域
程序计数器(PC) 因为程序计数器只是记录当前线程正在执行的那条字节码指令的地址,即使出现死循环都不会内存溢出
- lua程序设计(一)
摘要:lua程序设计第二版学习笔记 脚本语言的基础语法大都比较简单,这里只列举一些lua独有,或者需要特别注意的语法点. 书中前三章的内容是一些惯常的引言,基础数据类型,运算符等内容,相对简单,这里就 ...
- nginx篇最初级用法之三种虚拟主机基于域名\基于端口\基于IP地址端口的虚拟主机
在nginx中虚拟主机的类型与apache一样也有三种 1.基于域名的虚拟主机 2.基于端口的虚拟主机 3.基于IP地址端口的虚拟主机 在nginx配置文件中每一个server为一个虚拟主机如果需要多 ...
- CSPS模拟 90