你不知道的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 ...
随机推荐
- 深入了解一下HTTP缓存机制
HTTP 缓存机制作为 web 性能优化的重要手段,是Web 开发知识体系库中的一个基础环节,但是对于很多学习者来说,仅仅只是知道浏览器会对请求的静态文件进行缓存,但是为什么被缓存,缓存是怎样生效的, ...
- 一文读懂Spring MVC执行流程
说到Spring MVC执行流程,网上有很多这方面的文章介绍,但是都不太详细,作为一个初学者去读会有许多不理解的地方,今天这篇文章记录一下我学习Spring MVC的心得体会 话不多说,先上图: ...
- fread优化读入
inline char nc() { static const int BS = 1 << 22; static unsigned char buf[BS],*st,*ed; if(st ...
- LeetCode初级算法--树01:二叉树的最大深度
LeetCode初级算法--树01:二叉树的最大深度 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.n ...
- 【MySQL】事务隔离级别及ACID
注:begin或start transaction并不是一个事务的起点,而是在执行它们之后的第一个操作InnoDB表的语句,事务才真正开始.start transaction with consist ...
- html简介(1)
HTML 是用来描述网页的一种语言. HTML 指的是超文本标记语言: HyperText Markup Language HTML 不是一种编程语言,而是一种标记语言
- Mybaits 源码解析 (一)----- 搭建一个mybatis框架(MyBatis HelloWorld)
源码分析之前先搭一个mybatis的demo,这个在看源码的时候能起到了很大的作用,因为在看源码的时候,会恍然大悟,为什么要这么配置,为什么要这么写.(老鸟可以跳过这篇) 开发环境的准备 创建mave ...
- js数组及动态插入节点数据
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 如何学好javascript
今天逛论坛时看到有朋友问,是否有专门教Javascript的学校,这里想想把自己的一点建议和自己3年来的前端Javascript开发的经验跟大家分享下,也给出几本个人认为不错的书来做为大家学习的参考资 ...
- 【Windows系统】win10系统假死,窗口不能动,鼠标能动或不能动,最后蓝屏显示错误码: VIDEO_DXGKRNL_FATAL_ERROR 的解决办法
1. 问题的出现过程 最近博主自己用的 windows10 系统就碰到了这个问题. 一开始出现这个问题的征兆是鼠标会时不时地卡顿,一出现卡顿就会等个 1-3秒才能动.然后频繁卡顿(注意,这里根据经验就 ...