深入学习Java Scipt之作用域和闭包

引擎与作用域及编译器

在传统的编译语言的流程中,程序的一段源代码主要分成三步,统称为“编译”

  •   分词/词法分析

    它的主要作用是将字符组成的字符串分解成有意义的代码块,例如:var a=2;者会被分解成“var”,“a”,“=”,“2”。

空格是否被分解主要是看空格在该编程语言中有没有意义。

  •  解析/语法分析

  这个过程是将词法单元流(数组)转换成由元素逐级嵌套所组成的程序语法结构的树。这棵树被称为“抽象语法树”

  • 代码生成

  将AST转换成可执行代码的过程。如果不考虑具体细节的话,那么简单来说此过程便是将var a=2这一组AST转换成机器指令,用来创建一个叫做a的变量(分配内存),将2存储在a中。

  Java Script引擎要比这个过程复杂得多,但是它没有那么多时间来进行优化或者对代码的其他处理,因为Java Script代码通常在代码执行前的几微秒内进行编译和处理,看到这,也许你会有疑惑,为什么Java Script引擎能这么快呢?这是因为在我们讨论的作用域背后Java Script用尽了各种办法,将性能达到最优化。

作用域

  要想了解作用域是什么?起到怎么样的作用,必须还得了解以下的概念。

  • 引擎

  从头到尾负责代码的编译与运行

  • 编译器

  负责语法分析与代码生成

  • 作用域

  负责收集和维护所有声明的标识符组成的一系列查询,并实施一套非常严格的规则,判断当前执行的代码对变量访问的权限。通俗点来讲就像是仓库的管理员,它负责物品(声明的标识符)的安全(维护),以及的上架(收集)。对来访者(引擎或者编译器)取物品进行检查判断(判断权限)。

若你还是觉得很难理解,那没有关系,我们举个例子
 **例Java Script执行代码“ var a=2”。当引擎见到此声明时,它不仅会自己处理,还会将此声明抛给编译器进行处理。**

  **编译器的处理**:将“var a=2”分解为词法单元,由这些词法单元生成抽象语法树。然后接着生成代码,那么由伪代码描述“var a=2”,它的执行结果是这样的:“为一个变量分配内存,并命名为a,然后将2这个值保存在变量里面”。尽管看上去很完美,但是由于作用域的存在,编译器不得不进行另一种操作。

   遇到“var a=2”,编译器会先向作用域询问是否有一个该名称的变量存在于作用域中,是的话它就会忽略这次声明,继续编译。否的话,它就会要求作用域在当前作用域集合中创建一个名为"a"的变量,然后再编译。我们来按照之前的仓库理解,编译器是对商品(代码)加工的工人,当它遇到“var a=2”的物品清单(声明),它会将这个物品罗列出来(词法分析),然后看看物品之间的需求需要(抽象语法树),接着去仓库找物品(生成代码),在仓库的门口遇到了仓库管理员(当前的作用域集合),提出了物品要求,仓库管理员进库检索物品(查看是否创建了名为"a"的变量),若有,那么将物品递给工人(忽略此次声明);没有的话,则向总部提出此库("当前的作用域结合")物品进货(创建"a"变量)
   接下来编译器将为引擎运生成运行的代码,这些代码用来处理a=2这个赋值操作。引擎运行时会首先询问作用域,当前作用域中是否存在"a"这个变量,是的话,继续操作,不是的话,将在作用域中急促查找。
引擎最终找到了"a"这个变量,便会将2赋值给它,不然的话,它就会抛出一个异常。


  总结:在对"var a=2"的执行过程中,编译器首先对代码进行词法分析、语法分析,然后创建变量(如果作用域中不存在的话),运行时,引擎再一次在作用域中查询变量,如果找到的话就给它赋值。(经历了两次变量查询,此处的作用域指的是当前的作用域集合)

引擎查询变量的方式


  在编译器将声明生成可运行代码时,我们的引擎将会进行变量查找,引擎进行怎样的查找将会影响到查找的结果。
   在Java Script中引擎查找变量涉及到的查找方式主要有两种,LHS查询以及RHS查询,**LHS是在赋值操作左侧查找**,**RHS是赋值操作右侧查找**,在我们的例子中,“var a=2”涉及到的是LHS查询。
   例:
      console.log(a);
      在这里面就涉及到了RHS查询,怎么理解呢?在这代码里面"a"没有值,相应的它需要查找得到"a"的值,这样的话才能把值传给console.log();

我们把两个例子进行比较

      a=2

  在这里面我们并不需要查找"a"的值,而是要为"=2"这一个操作找到一个目标。

为了更加熟悉"LHS"以及"RHS",我们再举个例子

-------------------------------------------

  function foo(a){

console.log(a);

};

foo(2);

---------------------------------------------

  首先我们先看看foo()函数,console.log(a);中涉及到了"RHS"查询。

在foo(2)中我们也涉及到了"RHS"查询(查找foo()),但是,别忽略了,在此函数中将"2"的值传给形参"a"即"a=2"此处涉及到了"LHS"查询。

  这里面涉及到了几次"RHS"以及"LHS"查询呢?

foo(2)------一次"RHS'查询,查询foo()函数,

console.log(a)--------三次"RHS"查询,一次查询console,一次.log,一次"a"

在传递形参时-------一次"LHS"查询

总结:"LHS"是找到操作的容器,"RHS"是查找变量的值

那么为什么要区分LHS和RHS,这对我们调试Java Script是有好处的

  • 当作用域进行RHS查询时,查询不到结果时,它便会抛出ReferncError错误,而在查询到变量,但是对变量的引用不正确的情况下,会抛出TypeError错误。
  • 当作用域进行LHS查询时,在非ES5的严格模式下,它查询不到变量时,便会主动创建一个变量。而在ES5的严格模式下,它则会弹出ReferenceError错误。

作用域嵌套

  在我们举得例子中,如编译器对变量的查询,引擎对变量的查询都是在当前作用域集合中操作的,通常不仅只有这么一个作用域,而是多个。

  例如,当一个块或者函数在另一个块或者函数中时,便会发生作用域的嵌套,首先它们会在当前作用域下查询变量,没有查询到则返回外层作用域(直到最外层全局作用域)--------------这就是作用域的嵌套。

举个例子:

        function foo(a){

    console.log(a+b);

}        

var b=2;

foo(2);

在foo函数中我们需要对b进行RHS引用(查询b),但是在foo()函数这一层的作用域中,并没有查询到b,于是它便回到外层作用域查询b,在"var b=2"中查询到了b。

运用我们之前讲过的仓库例子,引擎是一名采购商人,它需要b这一个商品(RHS引用),于是去问了当前商店所在的仓库管理员(当前作用域),“这里有b这个商品吗?”,“没有,但是您可以去更高级别的仓库(外层作用域)找找”仓库管理员回答,于是采购商便去更高级别的仓库找到了b。

嵌套规则:从当前的作用域开始查找,向更高级别的作用域查找,直到最高级别的作用域,查找不到则停止抛出异常(根据RHS以及LHS不同抛出的异常不同)

 

异常

  为什么要知道LHS与RHS查询呢??这对我们调试Java Script代码是有好处的。

例如:   function(a){

console.log(a+b);

b=a;

}

foo(b);

  我们在foo()中对b进行RHS查询,但是在当前的作用域中查询不到b,根据作用域嵌套规则,我们返回最外一层,发现也没有b。此时我们抛出ReferenceError异常-------即RHS查询不到相关变量则抛出ReferenceError异常。

  而LHS则不同,如果在最外层的作用域中查询不到变量的话,它就会自作主张的创建一个变量,此时不会抛出异常

  但是在ES5的情况下,LHS表现则不同,在ES5(严格模式)下禁止自动或者隐式创建变量,这就意味着进行LHS查询不能够自动创建变量了!!!-------此时抛出ReferenceError异常

   RHS查询还有一个细节,就是当你查询到变量,但对变量的操作不正确时,也会抛出异常,例如在对一个数组执行大于数组下标的操作时--------抛出TypeError异常。

总结:

  • RHS查询不到变量时抛出ReferenceError异常,查询到变量对变量操作不正确时抛出Type异常

  • LHS在非ES5模式下,查询不到变量时,自动创建变量。在ES5模式下,查询不到变量时,抛出Reference异常

本章小结

  作用域实际上是一套用于确定变量的位置以及查询变量的规则。代码的编译分为三部分,词法分析--语法分析--代码生成。前两步是编译器在执行,直到生成可执行代码时,引擎才开始从作用域中查询变量。

  像Var a=2可分为两步

  1、声明变量:var a将会在当前的作用域内创建a变量,分配内存

  2、赋值操作:a=2会查询(LHS)变量a,然后进行赋值操作

  RHS查询是找到变量的值

  LHS查询是为变量进行赋值操作(包括实参形参的传递)

  RHS以及LHS查询都从当前作用域开始,一层一层往更高级作用域查询,直到全局作用域停止(找到变量立即停止)。

  当RHS没查询到变量时,抛出ReferenceError异常,查询到变量但变量操作不正确时,发生TypeError异常。

  不在严格模式(ES5)下,当LHS没查询到变量时,它自动隐式创建变量。在严格模式(ES5)下,LHS不允许自动隐式创建变量,抛出ReferenceError异常。

  

猜你喜欢

转载自blog.csdn.net/qq_41889956/article/details/83039693
今日推荐