作用域是什么
概念:所有程序都具有能够储存变量当中的值,并且能在之后对这个值进行访问或修改。
问题:这些变量住在哪里?换句话说,它们储存在哪里?最重要的是,程序需要时如何找到它们?
1.1 编译原理
尽管通常将JavaScript归类为“动态”或“解释执行”语言,但事实上它是一门编译语言。
程序中的一段源代码在执行之前会经历三个步骤,统称为“编译”。
编译
分词/词法分析(Tokenizing/Lexing)
这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token)。例如,考虑程序var a = 2;。这段程序通常会被分解成为下面这些词法单元:var、a、=、2 、;。空格是否会被当作词法单元,取决于空格在这门语言中是否具有意义。
这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token)。例如,考虑程序var a = 2;。这段程序通常会被分解成为下面这些词法单元:var、a、=、2 、;。空格是否会被当作词法单元,取决于空格在这门语言中是否具有意义。
解析/语法分析(Parsing)
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree, AST)。
var a = 2;的抽象语法树中可能会有一个叫作VariableDeclaration的顶级节点,接下来是一个叫作Identifier(它的值是a)的子节点,以及一个叫作AssignmentExpression的子节点。AssignmentExpression节点有一个叫作NumericLiteral(它的值是2)的子节点。
代码生成
将AST转换为可执行代码的过程被称为代码生成。这个过程与语言、目标平台等息息相关。
抛开具体细节,简单来说就是有某种方法可以将var a = 2;的AST转化为一组机器指令,用来创建一个叫作a的变量(包括分配内存等),并将一个值储存在a中。
1.2 理解作用域
LHS(左侧查询)和RHS(右侧查询)
LHS:如果查找的目的是对变量进行赋值,那么就会使用LHS查询;
RHS:如果目的是获取变量的值,就会使用RHS查询。
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2);
// foo()执行是会有一次RHS,形参2赋值给实参a时会有一次LHS,var b = a会拆成2个部分,var b 和 b = a 两个部分
// a往上查找会执行一次RHS, b=a会有一次LHS, return a + b时, a 和 b 各有一次RHS,
// 最后c = foo()是会有一次LHS // 合计:3次LHS,4次RHS
1.3 作用域嵌套
LHS和RHS引用都会在当前楼层进行查找,如果没有找到,就会坐电梯前往上一层楼,如果还是没有找到就继续向上,以此类推。一旦抵达顶层(全局作用域),可能找到了你所需的变量,也可能没找到,但无论如何查找过程都将停止。
function foo(a) {
console.log(a + b);
}
var b = 2;
foo(2); // 4
// 这里RHS和LHS都有往上一级作用域进行查找,console.log() 先查找console,在本级作用域没有,往上一级查找,找到全局对象,拿到console内置对象,然后查找log(),找到是内置对象里面的一个方法。有两次RHS操作,log(a + b) b在本级作用域中没有找到,往上一级查找,发现全局作用域中有声明b=2.
// 最终执行结果为4
1.4 异常
RHS查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError异常。
在进行LHS查询时,如果没有找到这个变量,全局作用域中就会创建一个具有该名称的变量,并返回,在严格模式中LHS查询失败时,并不会创建并返回一个全局变量,引擎会抛出同RHS查询失败时类似的ReferenceError异常。
如果RHS查询找到了一个变量,但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,或者引用null或undefined类型的值中的属性,那么引擎会抛出另外一种类型的异常,叫作TypeError。
ReferenceError同作用域判别失败相关,而TypeError则代表作用域判别成功了,但是对结果的操作是非法或不合理的。
1.5 小结
1.作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对变量进行赋值,那么就会使用LHS查询;如果目的是获取变量的值,就会使用RHS查询。
2.JavaScript引擎首先会在代码执行前对其进行编译,在这个过程中,像var a = 2这样的声明会被分解成两个独立的步骤:1、var a;2、a = 2;
3.在进行LHS查询时,如果没有找到这个变量,全局作用域中就会创建一个具有该名称的变量,并返回,在严格模式中LHS查询失败时,并不会创建并返回一个全局变量,引擎会抛出同RHS查询失败时类似的ReferenceError异常。
如果RHS查询找到了一个变量,但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,或者引用null或undefined类型的值中的属性,那么引擎会抛出另外一种类型的异常,叫作TypeError。