【一】js的词法作用域和动态作用域

前言

今年想做这样的一件事,想整理一下javascript的一些基础知识点并且分享出来,虽然我这博客没什么人来看,但是我希望还是万一有点点进来,能在我这找到所有他想要的,帮助人少走点弯路。
今天开第一篇词法作用域和动态作用域
为什么要讲这两个概念呢,因为这个是比较基础的一个基础概念,后面还引出作用域链闭包this啥的。

编译原理

大学的时候学java,android的时候,起个javaWeb,或者android app的时候总是要先等一下(其实是编译一下)才能预览到结果。
现在想想真恐怖,哪像前端,一刷新,完美!这么咋一看,会觉得javascript是”动态”的,其实它只是执行前编译。

编译器会干些什么呢?

  1. 分词/词法分析(Tokenizing/Lexing)
    当你输入var a = 2;,编译器首先会分词:var、a、=、2,至于空格,取决于空格在这门语言中是否具有意义。
  2. 解析/语法分析(Parsing)
    分词之后会把对应的又映射成AST(抽象语法树 Abstract Syntax Tree)
  3. 代码生成
    将上一步生成的AST转成可执行代码(其实也就是机器指令),创建一个叫作a的变量(包括分配内存等),并将一个值储存在a中。
    遇到var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中,如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域爱当前作用域的集合中声明一个新的变量,并命名为a。
    接下来a = 2,编译器先询问作用域,有就巴拉巴拉引用,没有就声明。

上面的三个步骤是传统编译要进行的步骤,javascript引擎会更复杂,具体自己google。我感觉自己心里大概清楚就可以了。比如引擎肯定是越做越好的,
比如会给你优化,会针对现在前端的流行框架(SPA)做对应的优化等,毕竟时代在发展嘛~

三个角色

  1. 引擎
    从头到尾负责整个Javascript程序的编译以及执行的过程
  2. 编译器
    负责语法分析以及代码生成等脏活累活
  3. 作用域
    负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套根据名称查找变量的规则,确定当前执行的代码对这些标识符的访问权限。

大致过程就是,先编译器和作用域配合生成可执行代码,在引擎和作用域配合去执行这串代码。

LHS和RHS

引擎查找变量的时候会进行LHSRHS查询。
作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对
变量进行赋值,那么就会使用 LHS 查询;如果目的是获取变量的值,就会使用 RHS 查询。
当发生作用域嵌套时,引擎查找变量时会一层一层上去查找,最后找不到,RHS查询模式会报ReferenceError错误,但是LHS不会报,相反它会为你创建一个全局的变量,
但是当你在严格模式下,也会报ReferenceError错误。这也就是一般情况下不用vara = 2最后结果会生成一个全局变量a

词法作用域

终于扯到正题了。
前面提过大部分标准语言编译器的第一个工作阶段叫作词法化。词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域 不变(大部分情况下是这样的)。
词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。
但是js有两个机制可以修改,一个是eval,一个是with。嘿嘿,不过我只用过eval,不常用,因为也不建议用。
前者可以对一段包含一个或多个声明的“代码”字符串进行演算,并借此来修改已经存在的词法作用域(在 运行时)。后者本质上是通过将一个对象的引用当作作用域来处理,将对象的属性当作作用域中的标识符来处理,从而创建了一个新的词法作用域(同样是在运行时)。
这两个机制的副作用是引擎无法在编译时对作用域查找进行优化,因为引擎只能谨慎地认 为这样的优化是无效的。

到这里,词法作用域就介绍完了,是不是很简单。知道这个有什么用呢?闭包的形成的根本原因就是这个词法作用域!当然我会再开篇说。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo() {
console.log( a );
}
function bar() {
var a = 3;
foo();
function t(){
var c = 1;
console.log(c);
}
t();
}
var a = 2;
bar();

想了一下还是举个例子吧。

  1. 全局作用域,有变量foobara
  2. bar函数内部作用域,有变量at,上级是全局作用域
  3. foo函数内部作用域,没有声明变量,只有一个对变量aRHS查询,上级是全局作用域
  4. t函数内部作用域,有变量c,上级作用域是bar函数内部作用域

所以ffoo函数打印的是2,t函数打印的是1。

动态作用域

知道了词法作用域概念,那相对的,动态作用域就是Js在运行的时候形成的作用域。举个栗子:

1
2
3
4
5
6
7
8
9
function foo() {
console.log( a );
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();

上面展示的代码,正常来说打印出来的a是2,因为js是遵循词法作用域的;但是如果是从动态作用域角度来看的话,打印出来的会是3。
如何解释?
词法作用域让foo()中的a通过RHS引用到了全局作用域中的a
词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定 的。(this也是!)词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。
你看你看,为啥要扯出动态作用域,this很像,当然,也是另开一篇再说。这个概念还是能扯很多的。

总结

这篇主要是扯点很基础的概念和机制,作为开篇,抛砖引玉哇,循序渐进哇。
另外,你不知道的js真的是本好书!推荐~

说来也惭愧,这篇文章开头是5月4号,现在已经是6月12号了。一个月过去了。
前段时间老板说要搞rn,巴拉巴拉去看rn,后来又说要做小程序,又巴拉巴拉小程序去了,这里就疏忽了。我会继续努力的~