第一部分:作用域是什么?
- 编译原理
- 理解作用域
- 作用域嵌套
- 异常
- 小结
编译原理
编译过程:
- 分词/词法分析
- 解析/语法分析
- 代码生成
1)分词/词法分析:
- 这个过程会将字符组成的字符串分解成有意义的 代码块 ,这些代码块被称为 词法单元。
- eg:var a = 2; 这段程序通常会被分解成 var 、a 、= 、2 、;
- 空格是否会被当作词法单元,取决于空格在这门语言中是否有意义。
- 分词和词法分析之间的主要差异在于 词法单元的识别是通过 有状态还是无状态的 方式进行的。
- 词法分析:词法单元生成器在判断a是一个独立的词法单元还是其他词法单元的一部分时,调用的是有状态的解析规则,这个过程就被称为词法分析。
2)解析/语法分析
- 将词法单元流(数组)转换成一个 “抽象语法树”。AST
- 这棵树是有元素逐级嵌套所组成的 代表程序语法结构的树。
3)代码生成
- 将 AST 转换成可执行代码的过程
- 创建并分配内存,并完成赋值操作
除了以上过程,js在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化。
js代码在执行前都要进行编译。js编译器首先会对代码片段进行编译,然后做好执行它的准备,并且马上就会执行它。
理解作用域
- 作用域是根据名称找变量的一套规则
- 引擎会对变量进行LHS、RHS查询。分别是赋值操作的左侧和右侧。查找过程通过作用域进行协助。
- LHS(左侧):试图找到变量的容器本身
- RHS(右侧):查找某个变量的值
- 当变量出现在赋值操作的左侧时进行LHS查询(a=2),出现在右侧是进行RHS查询(console.log(a);)。
a=2; //为=2这个操作找到一个目标
console.log(a); //关心的是这个值
function foo(a){
console.log(a);
}
foo(2);
// 2次RHS 1次LHS
找到LHS、RHS查询的次数?。
function foo(a){
var b = a;
return a+b;
}
var c = foo(2);
作用域嵌套
- 当一个块函数嵌套在另一个块函数中时,就发生了作用域的嵌套。
- 遍历嵌套作用域链的规则很简单: 引擎从当前的执行作用域开始查找变量, 如果找不到,就向上一级继续查找。 当抵达最外层的全局作用域时, 无论找到还是没找到, 查找过程都会停止。(当前作用域->上级作用域->…->全局作用域)
异常
- RHS查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError的异常。
- LHS
- 非严格模式:在全局作用域中也无法找到目标变量,就会创建一个,并将它返回给引擎
- 严格模式:禁止自动或隐式地创建全局变量,引擎抛出ReferenceError的异常。
- ReferenceError:同作用域判别失败相关
- TypeError:作用域判别成功,但是对结果操作是非法和不合理的。(eg:对一个非函数类型的值进行函数调用,或者引用null或undefined类型的值中的属性)
//RHS
console.log(a) //ReferenceError
var a;
a();
console.log(a)//TypeError
//LHS
"use strict";
function foo(a) {
// console.log(a+b);
b=a;
}
foo(2);//ReferenceError
function foo(a) {
// console.log(a+b);
b=a;
}
foo(2);
小结
1.作用域是一套规则,用于确定在何处以及如何查找变量(标识符)
- 如果查找的目的是对变量进行赋值,那么会使用LHS查询
- 如果查找的目的是获取变量的值,就会使用RHS查询
- 赋值操作符会导致LHS查询
2.在同一作用域中,对于同一个变量的多个声明,编译器只会编译第一个声明,其他的都会忽略
3.在编译阶段,编译器会完成声明和赋值两个操作。
// 答案:4次RHS 3次LHS