# 《你不知道的js》笔记
# 作用域和闭包
# 什么是作用域?
编译原理:
js是一门编译语言,但与传统编译语言不通,不是提前编译的,编译结果也不能在分布式系统中移植。
传统编译分为三步:
分词/词法分析
将代码分解成有意义的代码块(词法单元)
解析/语法分析
将词法单元流(数组)转换成一个由元素逐级嵌套的程序语法结构树(AST)抽象语法树
代码生成
将AST转换成可执行代码
js引擎编译要复杂的多,例如在语法分析和代码生成阶段会有特定的步骤对运行性能进行优化,包括对冗余元素进行优化。
- js引擎不会有大量的时间进行优化,因为js的编译不是发生在构建之前的
- 大部分情况下编译发生在代码执行前几微秒,js引擎用尽各种方法(比如JIT,可以延迟编译甚至重编译)来保证性能最佳
理解作用域:
引擎:从头到尾负责整个js程序的编译及执行过程
编译器:负责语法分析及代码生成
作用域:是可访问变量的集合,负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
变量的赋值操作会执行两个动作,一是在当前作用域中声明一个变量(该变量未被声明过),然后在运行时引擎会在作用域中查找该变量,对它赋值
LHS和RHS
LHS和RHS的含义是"赋值操作的左侧或右侧",并不一定意味着就是"=赋值操作符的左侧或右侧",应该理解成"赋值操作的目标是谁",以及"谁是赋值操作的源头(RHS)"
作用域嵌套
当一个块或函数嵌套另一个块或函数中,就发生了作用域嵌套
在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中查找,直到找到或者达到最顶层作用域(全局作用域)
异常
在变量没有声明的情况下,LHS和RHS的查询行为是不一样的
LHS查询到未声明的变量时,会在全局作用域下创建一个该名字的变量,并将其返回给引擎,前提是在非严格模式下
RHS查询到未声明的变量时,会抛出ReferenceError异常
如果RHS查询到一个变量,但是你对这个变量进行不合理的操作,如试图对一个非函数类型的值进行函数调用,或者引用null或undefined类型的值中的属性,则会抛出TypeError
# 词法作用域
# 词法阶段
大部分标准编译器的第一个工作阶段叫词法化,词法化的过程会对源代码中的字符进行检查,如果是有状态的解析过程,还会赋予单词语义
词法作用域就是定义在词法阶段的作用域。词法作用域是在写代码时将变量和块作用域写在哪里决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况是这样)
查找
作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的标识符,叫做遮蔽效应(内部的标识符遮蔽了外部的标识符)
全局变量会自动成为全局对象(比如浏览器中的window对象)的属性,因此可以不直接通过全局对象的词法名称,而是间接通过对全局对象属性的引用来对其进行访问
无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定