你不知道的JavaScript笔记
一、作用域和闭包
1. 作用域
1.1 编译原理
在传统编译语言的流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为“编译”。
- 分词/词法分析(Tokenizing/Lexing)
- 解析/语法分析(Parsing)
- 代码生成
任何 JavaScript 代码片段在执行前都要进行编译(通常就在执行前)。因此,JavaScript 编译器首先会对 var a = 2; 这段程序进行编译,然后做好执行它的准备,并且通常马上就会执行它。
1.2 理解作用域
- 引擎
从头到尾负责整个 JavaScript 程序的编译及执行过程。 - 编译器
负责语法分析及代码生成等脏活累活。 - 作用域
负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
总结:变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。
- LHS 与RHS
LHS查询是试图找到变量的容器本身,赋值的操作目标是谁。(look-up is trying to find the variable container itself)
RHS查询类似于简单的查找某个变量的值,retrieve his source value (取到它的源值),谁是赋值操作的源头。
- 作用域嵌套
在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。
如果 RHS 查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError异常。值得注意的是,ReferenceError 是非常重要的异常类型。相较之下,当引擎执行 LHS 查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非“严格模式”下。
严格模式在行为上有很多不同。其中一个不同的行为是严格模式禁止自动或隐式地创建全局变量。因此,在严格模式中 LHS 查询失败时,并不会创建并返回一个全局变量,引擎会抛出同 RHS 查询失败时类似的 ReferenceError 异常。
如果 RHS 查询找到了一个变量,但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,或者引用 null 或 undefined 类型的值中的属性,那么引擎会抛出另外一种类型的异常,叫作 TypeError。ReferenceError 同作用域判别失败相关,而 TypeError 则代表作用域判别成功了,但是对结果的操作是非法或不合理的。
2. 词法作用域
- eval
JavaScript 中的 eval(…) 函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。
function foo(str, a) {
eval( str ); // 欺骗!
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3
在严格模式的程序中,eval(…) 在运行时有其自己的词法作用域,意味着其中的声明无法修改所在的作用域。
function foo(str) {
"use strict";
eval( str );
console.log( a ); // ReferenceError: a is not defined
}
foo( "var a = 2" );
- with
JavaScript 中另一个难以掌握(并且现在也不推荐使用)的用来欺骗词法作用域的功能是with 关键字。可以有很多方法来解释 with,在这里我选择从这个角度来解释它:它如何同被它所影响的词法作用域进行交互。
with 可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,因此这个对象的属性也会被处理为定义在这个作用域中的词法标识符。
3. 函数作用域和块作用域
块作用域
1.try/catch
try {
undefined(); // 执行一个非法操作来强制制造一个异常
}
catch (err) {
console.log( err ); // 能够正常执行!
}
console.log( err ); // ReferenceError: err not found
err 仅存在 catch 分句内部,当试图从别处引用它时会抛出错误。
-
let
let 关键字可以将变量绑定到所在的任意作用域中(通常是 { … } 内部)。换句话说,let为其声明的变量隐式地劫持了所在的块作用域。 -
const
其值是固定的(常量)。之后任何试图修改值的操作都会引起错误。
4. 提升
函数声明和变量声明都会被提升,函数会首先被提升,然后才是变量。
5. 作用域闭包
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。