【共读】你不知道的js 上 (1)做用域是什么?

前言

本文会用思惟导图的形式列出本书该部分的知识点(剔除案例),构建知识脉络。因为是导读,正文部分只会列举部分的内容。本文适合未读过此书的同窗参考,另外读过此书的同窗,若是能纯熟得答出文初的问题,那么相信您对于这部分的内容能够说是记忆深入了。编程

建议在阅读前了解做者的平生,背景,核心贡献及思想。相信会对理解本书以及后续的选书读书会有所帮助。bash

豆瓣读书编程语言

问题

  1. 谈谈你对做用域的理解。
  2. 引擎,编译器,做用域分别是什么?它们如何共同协做?
  3. 介绍一下 ReferenceError 异常类型 和 TypeError 异常类型。
  4. 谈谈你对做用域链的理解。

做用域是什么?

谈谈你对做用域的理解。(我的理解,求拍砖)函数

做用域收集而且维护由全部声明的标识符组成的查询,有本身很是严格的规则肯定当前执行代码对标识符的访问权限。性能

JavaScript 是一门编译语言,在执行代码前的编译中,编译器须要和做用域沟通是否存在某个变量来决定建立仍是忽略。优化

接着引擎须要为变量赋值,它会经过 LHS查询 或者 RHS查询 查找变量,在当前做用域找不到时还要沿着做用域链一直往上往上找,若是在最外层的全局做用域也找不到,那么抛出叫作 ReferenceError 的异常spa

这里若是是使用 LHS查询 当全局做用域也不存在查找的变量时会自动建立并返还给引擎。code

1、简单介绍编译原理

JavaScript 是一门编译语言,可是它不像传统语言那样仅仅只经历编译的三个步骤,分词/词法分析,解析/语法分析,代码生成。cdn

咱们的 JavaScript 引擎要复杂的多,JavaScript 会用尽各办法(好比用JIT)来保证性能最佳。而且咱们要记住的是任何 JavaScript 代码片断在执行前都要进行编译,大部分状况下编译发生在代码执行前的几微秒(甚至更短)。对象

  • 分词/词法分析:这个过程会将字符串分解成对编程语言来讲有意义的词法单元(代码块)

    var a = zhengyang;
    复制代码

    以上代码会被分解成 var、a、=、; 空格是否会被当成语法单元取决于它是否在此处具备意义。

    分词和词法分析实际上是一件事,词在这里指的是带有某种归类的字符串,词经过词法来划分,分词是目的,词法分析是手段。

  • 解析/语法分析:这个过程会将词法单元流转化成一个由元素逐级嵌套所组成的表明程序语法结构的树,这个树叫作抽象语法树(Abstract Syntax Tree,AST)。

    var a = zhengyang;
    复制代码

    通过分词/词法分析,咱们把划分好的代码块组成抽象语法树,它有一个 VariableDeclaration(变量声明) 的顶级节点,下面是一个 Identitier (值为a)的子节点和一个叫作 AssignmentExpression(赋值表达式)的子节点,AssignmentExpression 有一个叫作 NumericLiteral(数值文字)的值为 2 的子节点。

  • 代码生成:将 AST 转化为课执行代码的过程被称为代码生成。

    简单来讲就是将 var a = 2AST 转化为一组机器指令来创造一个叫作 a 的变量,并将一个值存储在 a 中。

2、引擎,编译器,做用域分别是什么?它们如何共同协做?

  1. 引擎:从头至尾负责整个 JavaScript 的编译及执行过程。

  2. 编译器:引擎的同事负责语法分析及代码生成等脏活累活。

  3. 做用域:引擎的另外一位同事,负责收集而且维护全部声明的标识符组成的一些列咨询。它由一套很是严格个规则,肯定当前执行的代码对这些标识符的访问权限。

  4. 三位一体工做流:

var a = zhengyang
复制代码
  • 遇到 var a,编译器会咨询做用域是否已经存在该名称的变量存在于同一个做用域集合中。是,就忽略 var a 继续编译;不然就会要求在当前做用域集合中生命一个新的变量,命名为 a
  • 编译器会为引擎生成运行时所需的代码,用来处理 a = 2 这个赋值操做。引擎在运行时会先咨询做用域,当前的做用域集合中是否存在一个叫作a的变量。是,就会使用这个变量;否,引擎就会继续查找该变量。
  • 若是最后引擎找到了 a 变量会将 2 赋值给它;否,引擎会抛出一个异常。
  1. 编译器在第二步中生成了代码,引擎执行它的过程当中会查找 a 判断是否声明过,这个查找方式会影响最终的查找结果
  2. LHS查询RHS查询:简单来讲 LHS 查询就是当变量出如今赋值操做的左侧时进行的查询, RHS查询 就是变量出如今赋值操做的右侧时进行的查询。要注意,查找只会在当前做用域进行。
console.log(a) 
复制代码

以上代码就是 RHS查询 ,咱们能够看到变量 a 出如今右侧。

a = 2
复制代码

以上代码就是 LHS查询, 变量 a 出如今左侧。 6. 看一个具体例子

function foo(a) {
    console.log(a);//2
}
foo (2)
复制代码

以上代码首先进行的声明 foo 函数,变量在右因此使 RHS查询

而后是隐式的 a = 2 这里采用 RHS查询 ,变量在左因此使用 LHS查询

console.log console是内置对象,找 log 变量在右使用 RHS查询

console.log(a) 同上变量在右使用 RHS查询

3、做用域链是什么?

做用域是根据名称查找变脸的一套规则,当一个块或函数嵌套在另外一个块或函数中时就发生了做用域的嵌套。在当前做用域没法找到某个变量时,引擎就会在外层嵌套的做用域中继续查找,直到找到该变量为止。

function foo(a){
    console.log(a + b)
}
var b = 2
foo(2); //4
复制代码

上面的代码中 console.log(a + b) 咱们在函数做用域中找不到 b 只能在上层的全局做用域中找

遍历嵌套做用域做用域链的规则:引擎从当前的执行做用于开始查找变量,若是找不到就去上级继续查找。当抵达最外层的全局做用域时,若是尚未找到,那么查找就会中止。

如上图的一条做用域链,咱们在当前做用域要找到 a 、b、c 当前做用域没有就去外层做用域找在,找到了 b ,c没找到继续往外找,而后在全局做用域找到了 c 若是到此时尚未找到,那么查找就会中止。

4、ReferenceError 异常类型 和 TypeError 异常类型

  1. ReferenceError 异常类型
function foo(a) {
    console.log( a + b);
    b = a;
}
复制代码

以上代码会报 ReferenceError的异常,由于咱们经过 RHS 查询 在全部的嵌套做用域中都找不到 b。

相比之下若是是用 LHS查询 非严格模式下,若是在全局做用于中也找不到就会帮你建立一个具备该名称的变量,而且返还给引擎。

  1. 严格模式

严格模式禁止自动或隐式地建立全局变量。所以在严格模式中 LHS查询 失败时并不会建立并返回一个全局变量,而是会抛出 ReferenceError 异常。

  1. TypeError

若是经过 RHS查询 找到了一个变量,可是你尝试对这个变量的值进行不合理的操做,好比对一个非函数类型进行函数调用那么就会抛出 TypeError 异常。

5、小结

  1. JavaScript 是一门编译语言,它的编译过程不只仅是传统的三步,分词/词法分析,解析/语法分析,代码生成。还须要通过大量JIT这样的优化过程来保证性能。
  2. JavaScipt 引擎没有大量时间用来优化,他的编译过程不是发生在构建以前的而是在代码执行前的几微妙。
  3. 做用域是根据名称查找变量的一套规则,在 JavaScript 中若是在当前做用域找不到某个变量时,就会到外层嵌套的做用域中继续查找,若是在最外层的全局做用域当中也找不到那么查找就会中止。做用域链就是这一层层往外找的一条路径。
  4. 引擎从头至尾负责整个 JavaScrip t的编译及执行过程。编译器负责语法分析及代码生成。做用域负责收集而且维护由全部生命的标识符(变量)组成的一系列查询,并实施一套很是严格的规则肯定当前执行的代码对这些标识符的访问权限。
  5. 引擎,编译器,做用域的合做过程。
var a = 'zhengyang'
复制代码
  • 执行前的编译中,第一步,编译器会看变量是否在做用域中已经存在。是,忽略;不是,建立并命名为a。
  • 第二步,为引擎生成运行时所需的代码来处理 a = 'zhengyang' 这个赋值操做。
  • 衔接第二步,看变量在赋值操做的左边仍是右边引擎会使用 LHS查询 或者 RHS查询 在当前做用域查找变量。若是找到了引擎就会使用这个变量将 'zhengyang' 这个值赋给它。
  • 若是在当前做用域找不到就会经过做用域链向外找。若是最外层的全局做用域也找不到就会报 ReferenceError 异常。
  • 注意非严格模式下若是使用 LHS查询 ,当最外层的全局做用域也不存在要查找的变量时会自动建立而且返回该变量给引擎,严格模式下则不可,由于严格模式禁止自动或隐式建立全局变量。
相关文章
相关标签/搜索