作用域
-
编译
- 分词/词法分析
- 将字符串分解成有意义的代码块,也就是词法单元
- var a = 2; 分解成: var、a、=、2、;
- 空格是否会当作词法单元,取决于空格在这门语言中是否有意义
- 解析/语法分析
- 将词法单元流(数组)转换成代表程序语法结构的树(抽象语法树)(AST)
- 代码生成
- 将 AST 转换为可执行代码,与语言、目标平台相关
- 比如分配内存,存储值到变量等
- JavaScript 引擎要更复杂
- JavaScript 的编译过程不是发生在构建之前
- 大部分情况编译发生在代码执行前几微秒,甚至更短
- 各种优化,对冗余元素的优化
- 引擎
- 负责整个 JavaScript 程序的编译与执行过程
- 编译器
- 负责语法分析及代码生成等
- 作用域
- 负责收集并维护由所有声明的标识符组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
var a = 2;- 编译器首先将这段代码分解成词法单元
- 之后将词法单元解析成一个数据结构(AST)
- 首先 var a, 编译器会查看是否已经有一个叫做 a 的变量存在同一个作用域的集合中,如果是,编译器会忽略该声明,继续编译,否则会要求作用域在当前作用域集合中声明一个新变量,命名为 a.
- 然后编译器为引擎生成运行时所需代码,这些代码开始处理 a = 2 赋值操作, 引擎运行时首先在当前作用域中是否存在一个佳作 a 的变量,如果是,引擎会使用这个变量,如果否,引擎会继续向上一级作用域查找该变量(作用域链)
- 如果引擎最找找到 a 变量,则赋值为 2,否则引擎抛出一个异常
- 分词/词法分析
-
LHS 查询
- 赋值操作的目标是谁
-
RHS 查询
- 谁是赋值操作的源头
- 与查找某个变量的值一样
function foo(a) { var b = a; return a + b; } var c = foo(2); //LHS查询:var c, a = 2, var b //RHS查询: foo(2), = a, return a, b -
作用域嵌套(作用域链)
- 当一个块或函数嵌套在另一个块或函数中
- 在当前作用域中无法找到某个变量时,引擎会在外层嵌套的作用域中继续查找,知道找到该变量,后代到最外层作用域,也就是全局作用域为止
- LHS 和 RHS 很重要
- RHS 在所有嵌套作用域中找不到所需变量时,引擎会抛出 ReferenceError 错误
- LHS 查询时,如果在顶层(全局作用域)中无法找到目标变量时,会在全局作用域中创建一个该名称的变量,并返回给引擎(非严格模式),严格模式下也会抛出 ReferenceError
- 如果 RHS 查询到了变量,但尝试对变量的值进行不合理的操作(比如试图对一个非函数类型进行函数调用),引擎会抛出 TypeError
- ReferenceError 同作用域判别失败相关, TypeError 则代表作用域判别成功,但对结果的操作是非法或不合理的
-
词法(静态)作用域
- 闭包的关键
- 定义在词法阶段的作用域
- 欺骗词法
- eval
- with
- 影响性能,引擎的优化失效或者不优化
-
动态作用域
function foo(){ console.log(a); //静态作用域: 2, 动态作用域: 3 } function bar() { var a = 3; foo(); } var a = 2; bar(); -
函数作用域
- 隐藏内部实现
- 规避冲突
- 全局命名空间
- 模块管理
-
块作用域
- let
- 垃圾收集
- let 循环
- const
- with
- try/catch
- err 仅在 catch 的块内
- let
-
提升
a = 2; var a; console.log( a ); // 2 console.log( a ); var a = 2; // undefined- 两步
- 编译阶段: var a;
- 执行阶段: a = 2;
- 变量和函数声明从代码中出现的位置被移动到作用域最上面,这就叫做提升
- 先有声明后有赋值
- 只有声明会被提升,赋值或其他运行逻辑会留在原地
- 函数优先
- 函数首先会被提升,然后才是变量
foo() //1 var foo; function foo(){ console.log(1); } foo = function(){ console.log(2); }
- 两步
-
闭包
- 必须要理解作用域
- 普遍且明显
- 无处不在
- 词法(静态)作用域环境中
- 函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生的闭包
- 本质是 javascript 中的函数裕兴在它们被定义的额作用域里,而不是他们被执行的作用域里
- 模块
- 为创建内部作用域而调用一个包装函数
- 包装函数必须至少包括一个对内部函数的引用,这样就会创建涵盖整个包装函数内部作用域的闭包
- 模块机制的核心
ES6 模块var MyModules = (function Manager(){ var modules = {}; function define(name, deps, impl){ for (var i = 0; i < deps.length; i++){ deps[i] = modules[deps[i]]; } modules[name] = impl.apply(impl, deps); } })(); function get(name){ return modules[name]; } return { define: define, get: get }