作用域
1.1编译原理
- 分词/词法分析(Tokenizing/Lexing)
将字符串分解成代码块(词法单元)
分词和词法分析的差异在于词法单元的识别是通过有状态
还是无状态的
- 解析/语法分析(Parsing)
将词法单元流(数组)转换为由元素逐级嵌套组成的代表程序语法结构的树(抽象语法树AST) - 代码生成
将AST转换为可执行代码
1.2作用域
JS运行
- 引擎:负责JS编译与执行
- 编译器:负责语法分析以及代码生成
- 作用域:收集并维护所有声明的变量(标识符),根据规则确保执行代码对变量的访问权限
声明解析
|
|
1. `var a`编译器查询作用域是否存在变量`a`,有则忽略继续编译,无则要求作用域在当前作用域集合中声明一个为`a`的变量
2. 编译器生成运行代码给引擎,处理`a = 2`赋值操作。引擎询问作用域,当前作用域是否存在`a`,存在就使用,否则继续查找。
最终没有查找到将会报错。
LHS/RHS
变量出现在赋值操作左侧进行LHS,非左侧进行RHS
LHS:只查找
BHS:查找并取到值()
|
|
1.3作用域嵌套
作用域嵌套,在当前作用域无法找到变量时,引擎会在外层作用域中继续查找
1.4异常
RHS查询找不到会抛出异常
非严格LHS找不到会创建一个
严格模式LHS找不到也会抛出异常
RHS进行不合理操作会抛出TypeError
第二章:词法作用域
两种工作模型
查找:
遮蔽效应:内部标识符”遮蔽”外部标识符
2.2欺骗词法
欺骗词法作用域会导致性能下降
2.2.1 eval
eval中包含一个或多个声明会对作用域进行修改
严格模式下,eval有自己的词法作用域
setTimeout(),setInterval()第一个参数是字符串的时候,字符串可以被解释为动态生成的函数代码
new Function()接受代码字符串,转换为动态生成的函数
避免使用以上方式
2.2.2 with
严格模式下被禁止
2.2.3 性能
在编译阶段进行的性能优化,有些依赖于能够根据代码词法进行静态分析,预先确定所有变量和函数的定义位置,才能执行时快速找到标识符。
第三章,函数作用域和块作作用域
3.1 函数中的作用域
Js基于函数的作用域
含义:属于这个函数的全部变量都可以在整个函数范围内使用以及复用
3.2 隐藏内部实现
最小授权,最小暴露原则:最小限度的暴露必要内容
规避冲突:避免同名标识符之间的冲突
- 全局命名空间
通过全局作用域生命对象,将功能通过对象暴露给外界 - 模块管理
3.3 函数作用域
让函数名不污染所在作用域,并能够自动运行
函数表达式写法
如果function 是声明的第一个词,就是函数声明,否则是函数表达式
函数声明和函数表达式的区别:名称标识符绑定在何处
函数声明会绑定在所在作用域中
函数表达式会绑定在函数表达式自身的函数中
3.3.1 匿名和具名
匿名函数的缺点:
1. 调试困难
2. 引用自身需要`arguments.callee`
3. 可读性差
行内函数表达式可以指定函数名,所以给函数表达式命名是最佳实践
3.3.2 立即执行函数表达式IIFE
IIFE的两种写法
- (function(){})()
- (function(){}())
IIFE的用途
- 函数调用传参123(function(global){})(window)(function(undefined){})()可以确保undefined是undefined
2.倒置代码运行顺序
将需要运行的函数放在第二位,在IIFE执行之后当参数传递进去
3.4 块作用域
块作用域是对最小授权原则进行扩展的工具
3.4.1 with
用with从对象中创建出的作用域仅在with声明中有效
3.4.2 try/catch
catch会创建一个块级作用域,其中声明的变量仅在catch内部有效
3.4.3 let
let 可以将变量绑定到所在的任意作用域中({内部})
推荐显式的使用块级作用域,
- 垃圾收集123456789101112function process (data) {}var someReallyBigData = {}process(someReallyBigData)var btn = document.getElementById('my_button')btn.addEventListener('click', function click(evt) {//形成了闭包, 保留了整个外层作用域console.log('button clicked')}, false)
通过块级作用域,将变量进行本地绑定
- let循环
将i重新绑定到了循环的每一个迭代中1234567//代码说明{let jfor (j=0; j<10; j++) {let i = j}}
let声明属于一个新的作用域而不是当前函数作用域也不是全局作用域
3.4.4
const固定常量不可修改
4 提升
4.2 编译器与提升
包含变量和函数在内的所有声明都会在任何代码被执行前首先被处理
定义声明在编译阶段进行,只有声明本身会被提升
4.3 函数优先
函数与变量声明都会提升,函数优先
5 作用域闭包
5.2 闭包实质
定义:函数可以记住并访问所在的词法作用域时,就产生了闭包。
在自己定义的词法作用域之外执行,foo内存无法回收,导致foo内部作用域依然存在。
bar()依然持有对该作用域的引用,这个引用叫闭包
5.3 闭包深入
|
|
|
|
只要使用回调函数,实际上就是在使用闭包。
5.4 循环和闭包
|
|
定时器的回调函数在循环结束时才执行。
迭代内使用IIFE为每个迭代都生成一个新的作用域
延迟函数的回调在新的作用域封闭在每个迭代内部
块级作用域
let为每一次迭代生成块级作用域
5.5 模块
函数通过return,将内部函数暴露在外部,内部函数依旧保持函数的作用域。形成模块
|
|
模块模式的两个条件
- 必须有外部的封闭函数,至少被调用一次(创建模块实例)
- 封闭函数必须返回一个内部函数,内部函数才能在私有作用域中形成闭包
一个从函数调用所返回的,只有数据属性而没有闭包函数的对象并不是真正的模块
单例模式
命名将要作为公共API返回的对象
5.5.1 模块机制
|
|
5.5.2 未来模块机制
ES6的模块语法支持,将单个文件当做独立模块处理
基于函数的模块不稳定(不能被编译器识别/动态)
es6模块可以再编译器检查导入模块的API和成员是否真实存在(可以进行静态检查)
5.6 小结
当函数可以记住并访问所在的词法作用域,既是函数是在当前词法作用域之外执行,这时产生了闭包
模块的两个主要特征:
- 为创建内部作用域而调用包装函数
- 包装函数的返回值至少包括一个队内部函数的引用,这样才能创建涵盖整个包装函数内部作用域的闭包
动态作用域
词法作用域特征:定义在代码书写阶段
动态作用域:作用域链是基于调用栈的,而不是作用域嵌套
js并不具有动态作用域,但是this机制某种程度上像动态作用域
this词法
箭头函数将当前的词法作用域覆盖this本来的值
缺点:
- 容易混淆this绑定规则和词法作用域规则
- 匿名而非具名的