《你不知道的JS》-[P1] - 作用域是什么?

303 阅读3分钟

1.1、编译原理

js作为我们口中常说的“动态”或者“解释执行”语言,同时事实上他是一门编译语言,它与传统的编译一言非常相似,甚至更复杂。

一般来说,传统的编译语言编译过程中会经历三个步骤:

  • 1.分词/词法分析

  • 解析/语法分析

  • 代码生成

而js的编译一般发生在执行前的几微秒。

1.2、理解作用域

首先我们要知道在编译过程中,一共有三个角色参与进来,引擎、编译器和作用域

引擎负责了整个代码的编译及执行过程,编译器负责语法分析及代码生成等,作用域则收集和维护好所有声明的标识符。

举例:

var a = 2;

编译器首先会找作用域询问是否有名称为a的变量,有则忽略,否则会让作用域进行申明,

接下来编译器会为引擎生成运行时所需要的代码,也就是a = 2; (换句话说变量申明操作是由编译器做的,引擎只需要进行赋值操作)

随后引擎开始工作,它会找到作用域询问a变量是否存在,如果有,则使用这个变量,否则会继续往上级作用域进行查找(关于上级作用域在1.3的作用域嵌套中解释)。

最终如果找到a则引擎会进行赋值操作,否则会抛出异常。

总结:编译器负责对变量进行申明处理,引擎负责去作用域找申明的变量进行使用;

1.3、引擎的查询方式

首先,引擎分为LHS跟RHS两种查询方式,L与R可以理解成赋值操作的左侧与右侧

换句话说,当进行变量赋值时,进行LHS查询,如a = 2;当进行变量值查找时,进行RHS查询。如console.log(a);此时的a需要进行RHS查询。

注意:这种查询规则的定义在函数申明时并不适用。如function foo (a) {...},正常理解为var foo; foo = func...,那么这里的foo会进行一个LHS查询,事实上编译器在代码生成的同时,已经将foo的申明和值的定义同时处理了,所以在引擎执行代码时,不会再对foo进行赋值操作,固不会产生LHS查询。

1.4、作用域嵌套

当一个块或者函数在另一个块或者函数中,就发生了作用域的嵌套,当引擎无法在当前作用域中找到某个变量,则会一直往上级查找,直到找到该变量或抵达全局作用域中为止。不论LHS与RHS皆是如此。

当RHS查询在所有嵌套的作用域中未找到指定变量则会抛出ReferenceError的错误,而LHS查询找不到时则会自动在全局作用域中创建该变量并返回给引擎(非严格模式)。

如果RHS找到变量但对其进行不适当的操作(比如执行非函数类型变量),会抛出TypeError类型错误。

也就是说ReferenceError与作用域判别失败相关,TypeError则代表作用域判别成功,但操作不合法。

综上这些细节来看,区分LHS跟RHS是非常重要的。

测验:

请找出以下代码中的三处LHS与四处RHS。

function foo(a){
    var b = a;
    return a + b;
}
var c = foo(2)