-
Notifications
You must be signed in to change notification settings - Fork 0
Description
你不知道的JavaScript-上卷
目录
第一部分 作用域和闭包
第一章 作用域是什么
作用域: 能够存储变量当中的值, 并且能在之后对这个值进行访问或修改
1.1 编译原理
通常将JavaScript称为'动态'语言,或者'解释执行'执行语言, 但其实是 编译语言
- 分词/词法分析:
将字符组成的字符串,分解成编程语言有意义的代码块。 这些代码块叫做词法单元 (token) - 解析/语法分析:
将词法单元流(数组), 转换成一个由元素逐级嵌套所组成的程序语法结构的树。 这个树被称为抽象语法树(AST) - 代码生成
将AST转换成可执行代码的过程, 被称为代码的生成。
何时编译
与其他语言不同, JavaScript的编译过程不是发生在构建之前,
JavaScript: 编译发生在代码执行前的几微秒,甚至更短。
过程演示
var a = 2;
// 1.词法分析: 分解成词法单元 `var`、`a`、`=`、`2`
// 2.语法分析: 创建顶级节点 VariableDeclaration,
// 子节点 Identifier 值为a, 以及一个Literal的子节点 value为2
// 3.代码生成: 用来创建一个叫做a的变量(包括分配内存), 并将一个值存在a中1.2 理解作用域
- 引擎: 从头到尾负责JavaScript的编译及执行过程
- 编译器: 词法分析、语法分析及代码生成
- 作用域: 收集维护所有声明的表示符(变量), 确定当前执行代码对这些标识符的访问权限
变量的赋值
- 编译器在作用域中声明变量(已声明则忽略)
- 运行时引擎在作用域中查找变量
- 找到变量进行赋值
引擎查找变量规则
- LHS: 找到变量的容器本身, 为其赋值。
a = 2 - RHS: 找到源值。
console.log(a)
1.3 作用域嵌套
作用域链
引擎在当前的执行作用域查找变量, 如果找不到就向上一级查找,
当查找到最外层的全局作用域, 无论找到与否都会停止查找;
1.4 异常
-
RHS: 查询整个作用域链都找不到其值, 引擎将会抛出ReferenceError的异常错误
-
LHS: 查询整个作用域链都找不到,将在全局作用域创建一个该名称变量(非严格模式下), 严格模式下仍然会报错
-
ReferenceError: 和作用域相关的异常错误
-
TypeError: 作用域成功了, 但是对结果的操作非法
1.5 小结
- 作用域: 是一套规则, 用于确定何处如何查找变量(标识符)
- LHS: 查找的变量是对其赋值, 赋值操作;
- RHS: 查找的变量是为了获取值;
=操作符, 和调用函数传入的参数, 都会导致关联作用域的赋值操作
JavaScript引擎执行代码前,会先对代码进行编译, var a = 2; 被分解成两个步骤
var a在其作用域中声明新变量, 在代码执行前a = 2进行查询(LHS查询)变量a, 并为其赋值2
小测试
function foo(a) {
var b = a;
return a + b;
}
var c = foo(2);
// LHS查询 三处: c =.. 、 a = 2(隐式变量分配)、b=a
// RHS查询 四处: foo(2)、 = a 、 a +、 +b第二章 词法作用域
词法阶段:
词法作用域是由书写代码函时数声明的位置来决定,
作用域查找会在找到第一个匹配的标识符时停止(遮蔽效应)
欺骗词法
- eval: 对一个或者多个代码字符串进行演算, 并修改已存在的作用域(运行时)
- with: 对一个对象的引用当作作用域, 其属性当作标识符。从而创建一个新的词法作用域(运行时)
引擎无法在编译时 对作用域查找进行优化, 故两个都会导致代码变慢
词法作用域:
意味着作用域是由写代码时,函数声明的位置决定的
编译的词法分析阶段, 基本能够知道全部标识符在哪里及如何声明, 从而能预测执行过程中如何进行查找
第三章 函数作用域和块作用域
隐藏内部实现
- 把变量和函数包裹在一个函数的作用域中, 用这个函数来隐藏他们
- 最小授权或最小暴露原则
规避冲突:
隐藏作用域中的变量和函数所带来的另一个好处, 避免同名标识符之间的冲突
规避冲突方法:
- 全局命名空间: 多数库常用暴露一个对象
Jquery $ - 模块管理
函数作用域
匿名函数: 回调参数
setTimeout(function() {
console.log('I waited');
}, 0);
// 1. 匿名函数在栈追踪不会显示出有意义的函数名, 使得调试困难
// 2. 没有函数名,当函数需要引用自身时, 需要使用`arguments.callee`
// 3. 匿名函数省略了代码的可读性/可理解性很重要的函数名函数声明和函数表达式
- 函数声明: function 是声明中的第一个词,就是函数声明。(不可以省略函数名)
- 函数表达式: function 不是函数声明中的第一个词, 就是函数表达式
区别是他们的名称标识符将会绑定在何处
立即执行函数表达式
立即执行函数表达式 IIFE: (function IIFE(){console.log(1)})()使用()将函数包裹起来, 后面的()代表调用
块作用域
定义: 变量和函数不仅属于所处的作用域, 也可以属于某个代码块{...}
- with 关键字
- try/catch 中的catch会创建一个块作用域, 仅在catch中有效
- let
- const 其值是固定的常量
第四章 提升
包括变量和函数在内的所有声明都会在代码被执行前首先被处理。
提升
- 引擎会在解释JavaScript代码前进行编译, 编译阶段找到所有的声明, 并用合适的作用域将其关联起来
- var a = 2; 被解析成两个阶段: var a(编译阶段), a = 2;(执行阶段), 这个过程叫做提升;
- 普通块内部的函数声明通常会提升到所在作用域的最顶部
- 函数首先被提升, 接下来才是变量
- 声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会被提升
所有的声明(变量和函数)都会被移动到各自所在作用域的最顶端,这个过程叫提升
第五章 作用域闭包
闭包
- 函数是在当前词法作用域外执行
- 函数不在定义词法中执行, 但是依然保持对该词法作用域的引用,叫做闭包
- 闭包阻止了引擎的垃圾回收机制, 及释放不再引用的内存
模块
- 必须有外部的封闭函数, 该函数至少被调用一次(每次调用都会被创建一个新的模块实例)
- 封闭函数内部必须返回至少一个内部函数(形成闭包)
现代模块机制
调用了函数定义的包装函数, 并将返回值作为该模块的API
ES6模块
将文件作为独立的模块来处理, 每个模块都可以导入其他模块或者特定的API成员, 同时也可以导出自己的的API成员。