一、编译原理
词法分析
将字符串分解成代码块(也叫词法单元)例如: var a = 2;被解析为var、a、=、2 、;。
语法分析
将词法单元流转换成抽象语法树(由元素逐级嵌套所组成的代表了程序语法 结构的树)
{ "type": "Program", "body": [ { "type": "VariableDeclaration", //变量声明 "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", // 变量标记识别码 "name": "a" }, "init": { "type": "Literal", "value": 2, "raw": "2" } } ], "kind": "var" // 类型 } ], "sourceType": "script" }
代码生成
将 AST 转换为可执行代码的过程称被称为代码生成。例:将var a = 2; 的 AST转换成的机器指令,创建a变量,并将一个值存储在a中
二、javaScript作用域
引擎
负责javaScript程序的编译和执行过程
编译器
负责语法分析以及代码生成
作用域
负责收集并维护所有生成的变量组成一系列查询,并确定执行代码对这些变量的访问。也是一套根据名称查找变量的规则
js引擎在执行编译器生成的代码段时,需要作用域来配合。
引擎对变量的查找主要有俩种方式,LHS(赋值操作的左侧) RHS(赋值操作的右侧)。
引擎在碰到变量时,会沟通作用域来对变量进行查找。
作用域首先会在当前词法作用域来查找该变量,如果能找到则返回,如果找不到则去上层或者外层去找,直到找到该变量或者全局作用域查找完。
如果都没找到,则抛出ReferenceError异常。
嵌套作用域中可以定义名称相同的变量,作用域会在找到第一个停止,这叫遮蔽效应。
js中词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变。
但是js中有一些方法可以达到欺骗词法来改变作用域,例如:eval with等
在js中作用域又分为函数作用域(function)、块作用域(let、const)
闭包:当函数可以记住并访问所在的词法作用域时, 就产生了闭包, 即使函数是在当前词法作用域之外执行,这就是闭包特性 =>
1、闭包可以记住它的上下文
2、可以访问外部函数的参数(引用不会被销毁)
3、保存内部作用域(变量),阻止垃圾回收
如果将函数(访问它们各自的词法作用域)当作第一级的值类型进行传递,一般就会产生闭包,例如 回调函数、事件监听、axjs、webworker等。IIFE创建了闭包,但没有使用闭包特性。
闭包产生后,就可以在外部读取其他函数内部的变量,将函数内部和函数外部连接起来。