一些对JavaScript作用域的理解笔记

这是篇读书笔记,记录《你不知道的JavaScript》上卷的第一部分内容,同时也希望能帮助大家理解好作用域的知识点,梳理好的内容更容易理解和掌握

编译原理
理解作用域之前,我们先要理解编译原理。JavaScript引擎进行编译的步骤和传统的编译语言非常相似,一般都经过三个步骤:

  • 分词/词法分析
  • 解析/语法分析
  • 代码生成

分词/词法分析(Tokenizing/Lexing)这个过程会将由字符组成的字符串分解成有意义的代码块,这些代码块被称为词法单元。例如var a = 2;,会被拆分为vara=2;

解析/语法分析(Parsing)这个过程会将词法单元流(数组)转换为一个由元素逐级嵌套所组成的代表了程序语法结构的树,这个树被称为“抽象语法树”(AST)

代码生成这个过程会将AST转换为可执行代码

JavaScript引擎是要复杂得多,例如在词法分析和代码生成阶段有特定的步骤来对运行性能进行优化,也不会有大量的时间用来进行优化

理解作用域
引擎,从头到尾负责整个JavaScript程序的编译及执行过程

编译器,从属于引擎,负责语法分析和代码生成等工作

作用域,从属于引擎,负责收集并维护由所有声明的标识符组成的一系列查询

面对var a = 2;这段程序的时候,引擎、编译器和作用域之间的工作流程大致如下:

1、编译器将这段程序分解为词法单元
2、编译器将这段程序的词法单元解析成一个树结构(AST)
3、编译器根据这个AST去代码生成
4、编译器在代码生成阶段会询问作用域是否存在变量
5、编译器生成引擎所需的代码
6、引擎接到所需代码后,询问作用域是否有这个变量
7、引擎找到这个变量后,直接使用这个变量去完成赋值

引擎怎么查找这个变量,就是LHS和RHS的情况了

LHS查询是识图找到变量的容器本身,从而可以对其赋值;RHS查询则是找到某个变量的值

从这个过程中,我们可以看到作用域出现了两次,一次是编译器询问作用域是否存在变量,另一次是引擎询问作用域是否有这个变量

第一次可能会出现让作用域在它的集合内声明一个新的变量,而第二次则不会,只会去找

那么很显然,作用域就是一套给编译器和引擎看的规则,用于确定在何处和如果查找变量(标识符)

词法作用域
什么是词法作用域?

首先编译器的第一个工作是将程序分解为词法单元,这个阶段也叫词法化或单词化。在这个词法化阶段,词法分析器处理代码时会保持作用域不变,换句话说就是,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的。

例如

function foo(a){
    var b = a*2;
    function bar(c){
        console.log(a,b,c);
    }
    bar(b*3);
}
foo(2); //2,4,12

这个例子中有三个逐级嵌套的作用域
全局作用域只有一个标识符:foo;
foo所创建的作用域有三个标识符:a、bar和b
bar所创建的作用域只有一个标识符:c

无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置所决定

函数作用域
函数作用域?

属于这个函数的全部变量都可以再整个函数的范围内使用及复用,指的就是函数作用域

例如

function foo(a){
    var b = a*2;
    function bar(c){
        console.log(a,b,c);
    }
    bar(b*3);
}
bar(2); //ReferenceError
console.log(a,b,c); //ReferenceError

foo()所创建的作用域内有三个标识符,a、b、bar,bar()所创建的有标识符有c

函数的作用域?

区分函数声明与表达式最简单的方法是看function的关键字出现在声明中的位置,如果是第一个词,那么就是一个函数声明,否则就是一个函数表达式

函数声明与函数表达式之间最重要的区别在于它们的名称标识符将会绑定在何处

例如

var a = 2;
function foo(){
    var a = 3;
    console.log(a);
}
foo();
console.log(a);

这个foo被绑定在所在的作用域,所以可以直接调用

var a = 2;
(function foo(){
    var a = 3;
    console.log(a);
})();
console.log(a);

而这个foo被绑定在函数表达式自身的函数中,而不是所处的作用域中

块作用域
with关键字,是一个块作用域的例子,用with从对象中创建出的作用域仅在with声明中有效

try/catch的catch分句也会创建一个块作用域,其中的声明的变量仅在catch中有效

ES6引入的let和const关键字都可以创建块作用域变量

let关键字可以将变量绑定到所处的任意作用域中

例如

for(let i=0;i

评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据