JavaScript中的作用域(链)和闭包(一)

学JavaScript有很长一段时间了,做了一些网页,总是有一些bug或者不好的解决方案使我困扰,感觉是原生js的基础没有打好,最近开始巩固基础,首先对作用域和闭包的相关知识回顾并总结了一下

作用域

首先介绍一下编译原理:

JavaScript引擎进行编译的步骤和传统的编译语言相似,但某些环节可能更复杂。
传统语言的源代码在执行前会经历三个步骤。统称为“编译”。

分别是:分词/词法分析—— 解析/语法分析——代码生成。

N1:整个过程大概是,将代码中的字符组成的字符串分解成有意义的代码块,代码块也被称为词法单元token。另外,分词和词法分析之间的区别比较微妙。主要差异在于词法单元的识别是通过有状态还是无状态的方式进行的。简单来说,如果词法单元生成器在判断a是一个独立的词法单元还是其他词法单元的一部分时,调用的是有状态的解析规则,这个过程就被称为词法分析。接着数组类型的词法单元流转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。——抽象语法树。(AST)

N2:var a = 2;的抽象语法树中有一个VariableDeclaration的顶级节点,接下来是一个叫做Identifier(它的值是a)的子节点,以及一个叫做AssignmentExpression的子节点。AssignmentExpression节点有一个叫做NumericLiteral(它的值是2)的子节点。

N3:将AST转换为可执行代码的过程被称为代码生成。这个过程与语言、目标平台等息息相关。简单来说就是有某种方法可以将var a = 2;的AST转化为一组机器指令,用来创建一个叫做a的变量(包括分配内存等),并将一个值 储存在a中。

JavaScript引擎要复杂得多,在语法分析和代码生成阶段有特定的步骤来运行性能进行优化,包括对冗余元素进行优化等。而且,JavaScript引擎不会有大量的时间用来优化,因为js的编译过程不是发生在构建之前。大部分情况下编译发生在代码执行前的几微秒之内。编译后做好执行它的准备,并且马上就会执行它。实际上js引擎用尽了各种办法(JIT,可以延迟编译甚至实施重编译)来保证性能最佳。

理解作用域

引擎:负责整个js程序的编译及执行过程。
编译器:负责语法分析及代码生成。
作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前代码对这些标识符的访问权限。

接下来模拟引擎、编译器、作用域之间的协作(var a = 2;):
首先编译器在遇到var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则他会要求作用域在当前作用域的集合中声明一个新的变量,并命名为 a。
接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理a = 2这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫做a的变量。如果是,引擎就会使用这个变量;如果否,引擎会继续查找该变量。找到了就将2赋值给它,否则抛出异常。

总结:变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。

接下来介绍一些编译器的术语
编译器在编译过程的第二步中生成了代码,引擎执行它时,会通过查找变量a来判断它是否已声明过。查找的过程有作用域进行协助,但是引擎执行怎样的查找,会影响最终的查找结果。

引擎会为例子中的变量a进行LHS查询。另外一个查找的类型叫作RHS。
当变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询。
RHS查询与简单地查找某个变量的值别无二致,而LHS查询则是试图找到变量容器的本身,从而可以对其赋值。

可以将RHS理解成retrieve his source value (取到它的源值),这意味着“得到某某的值”。
“赋值操作的目标是谁(LHS)”“谁是赋值操作的源头(RHS)”

如果RHS查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError异常。当引擎执行LHS查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非严格模式下。如果RHS查询到一个变量,但是你尝试对这个变量的值进行不合理的操作,引擎会抛出另外一种类型的异常,TypeError。

举例:
function foo(a){
var b = a;
return a+b;
}
var c = foo(2);
1、找出所有的LHS查询
c=..;、a = 2(隐式变量分配)、b = ..
2、找出所有的RHS查询
foo(2..、=a);、a..、..b

JavaScript所采用的词法作用域:词法化的过程会对源代码中的字符进行检查,如果是有状态的解析过程,还会赋予单词语义。词法作用域就是定义在词法阶段的作用域。是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变。(无论函数在哪里被调用,也无论如何被调用,它的词法作用域都只由函数被声明时所处的位置决定)

猜你喜欢

转载自blog.csdn.net/hcjs_zee/article/details/78467467