【JS】做用域-1

读《你不知道的JS》的笔记,有问题请指出。html

简述做用域

每一个编程语言的一个最基本的功能,就是能够声明变量,在变量里储存值,更改值,访问值。
随之一系列问题产生,这些变量存储在哪里,未来须要使用他们的时候如何获取他们?
这表明咱们须要有一套设计良好的规则知道如何存储这些变量,并如何获取到他们,而这套规则,被称为做用域(Scope)。编程

编译和执行流程

不只是在执行时会用到做用域,编译时也会用到做用域编程语言

编译

咱们用 JS 写的代码称为源代码,是一种人类能看懂的语言,因为计算机只能读懂 0 和 1(二进制/机器语言),因此若是咱们要在计算机上执行咱们的代码,在执行代码以前,有个编译过程,目的是将源码编译成机器能够理解的机器码。函数

通常来讲,编译分为 3 个步骤设计

  1. 分词/词法分析(Tokenizing/Lexing)
    将咱们写的代码(字符串),分解成单独的、有意义的代码块,这种代码块也称为词法单元(token),只抽取有意义的部分


    注:分词≠词法分析,他们是有细微区别的,区分他们的一个最直接的方式就是,好比 var a = 2这行代码,当词法单元生成器(tokenizer)在判断 var 是单独的一个词法单元仍是属于其余词法单元的一部分时,若是调用的是有状态(stateful)的解析规则,那么字符串被转换成词法单元的这个过程就被称为词法分析,不然,就是分词。
    待解决:这里面说的有状态是什么是什么意思?
  2. 解析/语法分析
    将上面已经转换好的词法单元转换成抽象语法树(AST)
  3. 代码生成
    将 AST 转换成机器能够识别的指令,让机器执行 var a = 2 这一系列任务。

编译完以后,就是执行代码了,对于 JS 来讲,即便是 var a = 2 这行很简单的代码,在通过编译和执行这两个步骤时,也会涉及到做用域。code

JS 的编译过程和其余语言的编译过程有点不一样,在编译的第三步,也就是将抽象语法树(AST)转换成机器指令时,当编译器碰到声明操做时,如 var a = 2,编译器会询问做用域,在当前的做用域内,是否声明过 a,若是没有,就会让做用域在当前做用域内声明一个 a,不然,忽略该声明。htm

从上面能够看出,在 JS 引擎真正执行代码以前,编译过程当中,编译器就会将变量先在做用域内声明好。token

待解决:上面用的是 var,那么我用 let 声明变量的话,他也会帮我提早在做用域内声明好吗?作用域

执行

代码编译完成后,就是执行步骤了,执行是由 JS 引擎执行,在执行 var a = 2 这行代码时,也会涉及到做用域:字符串

便于理解,接下来咱们把它们拟人化,每一个人工做上都有本身的工做职责,他们也是同样,对于做用域来讲,他的职责就是管理他这块区域的变量,这个区域里,存在哪些变量,变量分别存储的值是多少他都知道,也是他应该知道的,因此在以前的编译过程当中,编译器在声明 a 以前,首先跑去问了做用域确认这块区域里尚未变量 a 后,才让做用域在这块区域内声明了变量 a。

而编译完成后,也就是代码执行过程,JS 引擎这时看到 var a = 2 后,也会先去问做用域,在当前做用域下,是否已经存在变量 a 了呢?

做用域检查了下当前的他这块区域内的变量,回答说,“嗯嗯,已经有了,是刚刚编译的时候编译器声明的”。

JS 引擎:“好嘞,既然有了,那我就不用重复声明了,我就赋个值就好了。”

LHS && RHS
JS 引擎在向做用域询问变量的时候,查询的方式还能够细分为 LHS 和 RHS,也就是让做用域查询这个变量是否存在,仍是让做用域查询这个变量的值。

var a = 2 
console.log(a)

代码如上,仍是以对话的形式

JS 引擎:做用域大哥,帮我看看你那里有没有变量 a 啊,我得给他赋个值。(查看变量容器自己是否存在,属于 LHS 查询方式)

做用域:找到了!这家伙在我这

JS 引擎: 谢谢了,再帮我看看 console 变量的值呢,我找找他里面有没有 log 这个方法(查询 console 变量的值,属于 RHS 查询方式)

做用域:有的,console 的值给你了,你看看吧

JS 引擎:好嘞,谢谢,我看一下。。有 log 这个方法!做用域,再帮我看下 a 变量的值呢,虽然我刚刚给他赋值了一个 2,可是我仍是得确认一下(查询变量 a 的值,属于 RHS 查询方式)

做用域:嗯嗯,我看了下,他的值仍是2

JS 引擎:好的,谢谢!

做用域链

有的时候,做用域是嵌套的

// 最外层为全局做用域
var a = 2 

function test() {
// 对于 JS 来讲,一个函数会生成一个做用域(先不提 let 生成的块级做用域)
    console.log(a)
}

test()

对于 test 函数来讲,他本身内部就有一个做用域 A,最外层有一个全局做用域。

JS 引擎在调用 test 函数的时候,因为须要打印变量 a,所以向 test 函数内部的做用域 A 求助,问他有没有看到变量 a,做用域 A 说没有看到,因而 JS 引擎向做用域 A 外层的做用域(全局做用域)求助,最终在全局做用域里找到了变量 a。

有时候并不必定当前做用域的外层做用域就是全局做用域,可能还嵌套有其余做用域,可是查询方式都是同样的,当前做用域找不到,就沿着嵌套的做用域往外找,直到找到全局做用域。

若是在全局做用域内也找不到这个变量呢?

那就可能会报错了,可是具体的报错信息仍是有区别的:

  1. 查询变量自己(LHS 查询方式)

    • 在非严格模式下,找到全局做用域都没有找到这个变量,全局做用域就会直接帮咱们在全局做用域内声明这个变量,也就是全局变量;
    • 在严格模式下,会抛出 referenceError 的错误,表明咱们想引用的变量不存在。
  2. 查询变量的值(RHS 查询方式)
    若是咱们对变量的值使用方式有错,好比 console.log 是一个函数,咱们却想从里面获取一个不存在的属性,如 console.log.test.sss,从一个 undefined 的数据类型上获取 sss 属性,就会抛出 typeError 错误
相关文章
相关标签/搜索