你不知道的JS

(更新中)

作用域和闭包

编译原理

传统编译语言

分词/词法分析

将由字符组成的字符串分解成有意义的代码块(词法单元 token)

var a = 2; 会被拆为 var、a、=、2、;

解析/语法分析

将词法单元流(数组)转换成抽象语法树(Abstract Syntax Tree, AST)

var a = 2; 的抽象语法树中可能会有一个叫作 VariableDeclaration 的顶级节点,接下来是一个叫作 Identifier(它的值是a)的子节点,以及一个叫作AssignmentExpression 的子节点。AssignmentExpression 节点有一个叫作NumericLiteral(它的值是2)的子节点。

代码生成

将 AST 转换为可执行代码的过程被称为代码生成。

简单来说就是有某种方法可以将var a = 2; 的 AST 转化为一组机器指令,用来创建一个叫作a 的变量(包括分配内存等),并将一个值储存在a 中。

总结

image-20220710153154238

JS 的编译过程

复杂得多(时间短,需要优化性能)

对于JS来说,大部分情况下编译发生在代码执行前的几微秒(甚至更短!)的时间内。在我们所要讨论的作用域背后,JavaScript 引擎用尽了各种办法(比如JIT,可以延迟编译甚至实施重编译)来保证性能最佳。

简单地说,任何JavaScript 代码片段在执行前都要进行编译(通常就在执行前)。因此,JavaScript 编译器首先会对var a = 2; 这段程序进行编译,然后做好执行它的准备,并且通常马上就会执行它。

处理工具

引擎

从头到尾负责整个JavaScript 程序的编译及执行过程。

编译器

负责语法分析及代码生成

作用域

负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限

var a = 2 后发生了什么

编译器首先会将这段程序分解成词法单元,然后将词法单元解析成一个树结构。然后开始生成代码。

在生成代码的过程中:变量的赋值操作会执行两个动作。

  1. 首先编译器会在当前作用域中声明一个变量(如果之前没有声明过,否则忽略声明继续编译)

  2. 然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值(找不到就抛出异常)。

引擎如何查找变量

LHS 查询

如果查找的目的是对变量进行赋值,那么就会使用LHS 查询

a = 2

RHS 查询

如果目的是获取变量的值,就会使用RHS 查询。

console.log(a)

作用域

作用域

作用域是根据名称查找变量的一套规则。

作用域链

当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。

词法作用域

大部分标准语言编译器的第一个工作阶段叫作词法化

词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,是静态的。

欺骗词法

欺骗词法作用域会导致性能下降

eval

动态生成代码(即通过代码生成代码)

1
2
3
4
5
6
function foo(str, a) {
eval( str ); // 欺骗!
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3

严格模式下,eval(..) 在运行时有其自己的词法作用域,无法欺骗

1
2
3
4
5
6
function foo(str) {
"use strict";
eval( str );
console.log( a ); // ReferenceError: a is not defined
}
foo( "var a = 2" );

with

with 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。

严格模式禁止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj = {
a: 1,
b: 2,
c: 3
};
// 单调乏味的重复"obj"
obj.a = 2;
obj.b = 3;
obj.c = 4;
// 简单的快捷方式
with (obj) {
a = 3;
b = 4;
c = 5;
}

存在全局变量泄露的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function foo(obj) {
with (obj) {
a = 2;
}
}
var o1 = {
a: 3
};
var o2 = {
b: 3
};
foo( o1 );
console.log( o1.a ); // 2
foo( o2 );
console.log( o2.a ); // undefined
console.log( a ); // 2——a 被泄漏到全局作用域上了

性能问题

性能很差,不要使用!

原因:JavaScript 引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。使用 eval 或 with 会使他们失效。


你不知道的JS
http://example.com/2022/07/10/你不知道的JS/
Author
John Doe
Posted on
July 10, 2022
Licensed under