
词法作用域
传统编译语言的源代码在执行前会经历三个步骤,统称为编译:
- 词法分析,将源代码分解为词法单元;
- 语法分析,将词法单元流转换为抽象语法树(AST);
- 代码生成,将 AST 转换为可执行代码(一组机器指令)。
JS 是一门解释型语言,在执行前也需要进行编译,不过它的编译不是发生在构建前而是在运行时。
JS 对变量有两种查询方法:LHS 和 RHS;LHS 用来找到变量本身,RHS 用来查找变量的值。作用域就是查询变量的规则,如果查找的目的是对变量赋值则进行 LHS 查询;如果查找的目的是获取变量的值则进行 RHS 查询。
作用域有两种工作模型:动态作用域和词法作用域,JS 采用词法作用域。词法作用域就是定义在词法阶段的作用域,是由变量和块作用域的位置来决定的。动态作用域基于调用栈,是在运行时确定的,this 就像是动态作用域的表亲。
欺骗词法作用域:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
function foo(str) {
eval(str); // 欺骗,修改词法作用域并对全局变量 a 进行遮蔽
console.log(a);
}
var a = 1;
foo('var a = 2'); // 2
function bar(str) {
'use strict';
eval(str); // 严格模式下,eval 有自己的作用域,无法欺骗
console.log(b);
}
var b = 1;
bar('var b = 2'); // 1
function we(o) {
with (o) {
c = 2;
}
}
var o1 = {
c: 1
};
var o2 = {
d: 1
};
we(o1);
we(o2);
console.log(o1.c); // 2
console.log(o2.c); // undefined
console.log(c); // 2,c 被泄露到全局作用域
|
闭包
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
1
2
3
4
5
6
7
8
9
10
|
function foo() {
var a = 2;
function bar() {
// 拥有涵盖 foo 内部作用域的闭包,bar 可以在之后任何时间位置引用
console.log(a);
}
return bar;
}
var fn = foo();
fn(); // bar 拥有对 foo 内部作用域的引用,而这个引用就叫作闭包
|
运算符优先级
this
this 的行为很像动态作用域,是在运行时进行绑定的。当一个函数被调用时,会创建一条活动记录(执行上下文),这条记录会包含函数在哪里被调用(调用栈)、调用方式、传入参数等信息,this 就是这条记录的一个属性。
this 的绑定规则:默认绑定、隐式绑定、显示绑定、new 绑定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
// 默认绑定在 window 上
function foo() {
console.log(this); // window
}
foo();
// 在严格模式下,默认为 undefined
function bar() {
'use strict';
console.log(this); // undefined
}
bar();
// 隐式绑定
var o = {
a: 1,
foo
};
// 调用时,对象 o 拥有 foo
o.foo(); // o
// 隐形丢失,这里 o.foo 只是 foo 的引用
setTimeout(o.foo, 0); // window
// 显示绑定
var g = {
list: [1, 2, 3]
};
g.list.forEach(function () {
console.log(this.list);
}, g);
// new 绑定
function V(name) {
this.name = name;
}
var v = new V('case');
console.log(v.name); // case
|
在箭头函数中,放弃了 this 的绑定规则,用当前的词法作用域取代了 this 机制。
== 与 ===
常见的误区是:“== 检查值是否相等,=== 检查值和类型是否相等”;正确的解释是:“== 允许在相等比较中进行强制类型转换,而 === 不允许”。实际上 == 的工作量要大一些。
1
2
3
|
// == 最容易出错的一个地方是 true 和 false 与其他类型之间的相等比较,要避免布尔值的宽松相等
console.log('42' == true); // false
console.log('42' == false); // false
|
子类构造器
对于类,构造器不是必须的,如果省略,二者都会提供默认的构造器;默认子类构造器自动调用父类构造器并传递所有参数。
1
2
3
4
5
6
7
8
9
10
|
class Child extends Parent {
constructor(...args) {
// super 指向 Parent 的构造函数
super(...args);
}
hello() {
// super 指向 Parent.prototype
super.hello();
}
}
|