你不知道的JavaScript--作用域(一)

时间:2022-03-11 14:45:18

第一部分:作用域是什么?

  • 编译原理
  • 理解作用域
  • 作用域嵌套
  • 异常
  • 小结
编译原理

编译过程:

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

1)分词/词法分析:

  • 这个过程会将字符组成的字符串分解成有意义的 代码块 ,这些代码块被称为 词法单元。
  • eg:var a = 2; 这段程序通常会被分解成 var 、a 、= 、2 、;
  • 空格是否会被当作词法单元,取决于空格在这门语言中是否有意义。
  • 分词和词法分析之间的主要差异在于 词法单元的识别是通过 有状态还是无状态的 方式进行的。
  • 词法分析:词法单元生成器在判断a是一个独立的词法单元还是其他词法单元的一部分时,调用的是有状态的解析规则,这个过程就被称为词法分析。

2)解析/语法分析

  • 将词法单元流(数组)转换成一个 “抽象语法树”。AST
  • 这棵树是有元素逐级嵌套所组成的 代表程序语法结构的树。

3)代码生成

  • 将 AST 转换成可执行代码的过程
  • 创建并分配内存,并完成赋值操作
除了以上过程,js在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化。
js代码在执行前都要进行编译。js编译器首先会对代码片段进行编译,然后做好执行它的准备,并且马上就会执行它。
理解作用域
  • 作用域是根据名称找变量的一套规则
  • 引擎会对变量进行LHS、RHS查询。分别是赋值操作的左侧和右侧。查找过程通过作用域进行协助。
  • LHS(左侧):试图找到变量的容器本身
  • RHS(右侧):查找某个变量的值
  • 当变量出现在赋值操作的左侧时进行LHS查询(a=2),出现在右侧是进行RHS查询(console.log(a);)。
a=2;    //为=2这个操作找到一个目标
console.log(a);  //关心的是这个值


function foo(a){
    console.log(a);
}
foo(2);
// 2次RHS 1次LHS

找到LHS、RHS查询的次数?。
function foo(a){
    var b = a;
    return a+b;
}
var c = foo(2);
作用域嵌套
  • 当一个块函数嵌套在另一个块函数中时,就发生了作用域的嵌套。
  • 遍历嵌套作用域链的规则很简单: 引擎从当前的执行作用域开始查找变量, 如果找不到,就向上一级继续查找。 当抵达最外层的全局作用域时, 无论找到还是没找到, 查找过程都会停止。(当前作用域->上级作用域->…->全局作用域)
异常
  • RHS查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError的异常。
  • LHS
    • 非严格模式:在全局作用域中也无法找到目标变量,就会创建一个,并将它返回给引擎
    • 严格模式:禁止自动或隐式地创建全局变量,引擎抛出ReferenceError的异常。
  • ReferenceError:同作用域判别失败相关
  • TypeError:作用域判别成功,但是对结果操作是非法和不合理的。(eg:对一个非函数类型的值进行函数调用,或者引用null或undefined类型的值中的属性)
//RHS
console.log(a) //ReferenceError

var a;
a();
console.log(a)//TypeError

//LHS
"use strict";
function foo(a) {
    // console.log(a+b);
    b=a;
}
foo(2);//ReferenceError

function foo(a) {
    // console.log(a+b);
    b=a;
}
foo(2);
小结

1.作用域是一套规则,用于确定在何处以及如何查找变量(标识符)

  • 如果查找的目的是对变量进行赋值,那么会使用LHS查询
  • 如果查找的目的是获取变量的值,就会使用RHS查询
  • 赋值操作符会导致LHS查询

2.在同一作用域中,对于同一个变量的多个声明,编译器只会编译第一个声明,其他的都会忽略
3.在编译阶段,编译器会完成声明和赋值两个操作。

// 答案:4次RHS 3次LHS