第1章 作用域是什么
- JS不是提前进行编译的,大部分情况下编译发生在代码执行前的几微秒
- 编译的三个步骤:
- 分词/语法分析:将由字符组成的字符串分解成有意义的代码块(词语单元)
- 解析/语法分析:将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树(抽象语法树AST)
- 代码生成:将AST转换为可执行代码的过程
- 引擎:负责JS编译及执行过程
- 编译器:语法分析及代码生成
- 作用域:收集并维护由所有声明的标识符(变量)组成的一系列查询
- 变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。
- LHS:对目标进行赋值
- RHS:取到变量的值
- ReferenceError:RHS查询在所有嵌套的作用域中遍寻不到需要的变量时抛出的异常
- TypeError:对变量的值进行不合理的操作抛出的异常
第2章 词法作用域
- 作用域有两种主要的工作模型:词法作用域、动态作用域
- 词法作用域:定义在词法阶段的作用域。词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的。
- 遮蔽效应:作用域查找会在找到第一个匹配的标识符时停止
- 词法作用域查找只会查找一级标识符。
欺骗词法
- 词法作用域由函数声明时所处的位置决定,在运行时欺骗词法作用域的两种方法:
- eval(..):动态创建的代码,将代码欺骗和假装成书写时代码就在那里,来实现修改词法作用域环境
- with(…):重复引用同一个对象中的对个属性的快捷方式。with块可以将对象处理为词法作用域,但是这个块内部正常的var声明并不会被限制在这个块的作用域中,而是会被添加到with所处的函数作用域中。
性能
- JS引擎会在编译阶段进行数项性能优化,有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置
- 在代码中出现eval()和with()只能简单地假设关于标识符位置的判断都是无效的,故引擎无法在编译时对作用域查找进行优化
第3章 函数作用域和块作用域
- 最小授权或最小暴露原则:在软件设计中,应该最小限度地暴露必要内容,而将其他内容都“隐藏”起来。
规避冲突
- “隐藏”作用域中的变量和函数所带来的另一个好处,是可以变同名标识符之间的冲突。
- 两种方法:
- 设置对象为一个全局命名空间
- 通过依赖管理器的机制将库的标识符显式地导入另外一个特定的作用域中
函数作用域
function foo(){..}
是函数声明,被绑定在所在作用域中,直接通过foo()来调用它(function foo(){..})
是函数表达式,被绑定在函数表达式自身的函数中,在…代表的位置访问foo。- 函数表达式可以是匿名的,函数声明不可以省略函数名
- IIFE(立即执行函数表达式):
(funtion(){...})()
或(funtion(){...}())
函数表达式加上括号 - IIFE:把他们当做函数调用并传递参数进去。
- IIFE会通过声明并立即执行一个函数来创建作用域
块作用域
- 创建块作用域的方法
- with
- try/catch
- let和const声明
第4章 提升
- 先有声明后又赋值。只有生命本身会被提升,赋值或其他运行逻辑会留在原地。
- 函数声明会被提升,但是函数表达式并不会被提升
- 在多个“重复”声明的代码中,函数会首先被提升,然后才是变量。
- 重复声明的代码中,后面出现的函数声明可以覆盖前面的函数声明
第5章 作用域闭包
- 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
- 只要使用了回调函数,实际上就是在使用闭包
- IIFE并不是在它本身的词法作用域以外执行的,是在定义时所在的作用域中执行。
模块
- 最常见的实现模块模式的方法是模块暴露
- 模块模式具备两个必要条件:
- 必须有外部的封闭函数,该函数必须至少被调用一次
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
- 基于函数的模块并不是一个能够被稳定识别的模式(编译器无法识别),它们的API语义只有在运行时才会被考虑,因此可以在运行时修改一个模块的API。
- ES6的模块API更加稳定,可以再编译器检查对导入模块的API成员的引用是否真实存在。
动态作用域
词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的。
JavaScript并不具有动态作用域,只有词法作用域。
1
2
3
4
5
6
7
8
9
10function foo(){
console.log(a);
}
function bar(){
var a=3;
foo();
}
var a=2;
foo(); //2
bar(); //2
This词法
- 可能会丢失this绑定的问题,最常用的解决方案是
var self=this
- 箭头函数放弃了所有普通this绑定的规则,是用当前的词法作用域覆盖了this本来的值
- 箭头函数不够理想的原因:
- 箭头函数混淆了this绑定规则和词法作用域规则
- 箭头函数是匿名的,而非具名的