跳到主要内容

作用域与变量声明详解

作用域

在函数执行前一刻,[[scope]] 会被创建,这是一个包含当前执行环境的对象。如果函数执行时产生了激活对象(AO),那么全局对象(GO)的作用域将位于 AO 之后。

函数的预编译

函数在执行前会进行预编译,具体步骤如下:

函数创建激活对象(AO

形参被赋值为 undefined

形参被赋值为实际参数

函数体被赋值

执行函数

KISS 原则

KISS 代表 keep it simple stupid,即保持简洁原则。在设计过程中,应注重简约,避免不必要的复杂性。大多数系统设计应保持简洁和单纯,以实现最佳运行效果。因此,简单性应成为设计的关键目标,尽量避免引入复杂性。这一原则同样适用于商业书信、电脑软件动画工程设计等领域。

块级作用域

{
}

let

在同一作用域下,let 不可重复声明,而 var 可以。

let a = 1;
let a = 2;
// SyntaxError: Identifier 'a' has already been declared

被大括号包裹的内容视为一个作用域。

function test(a) {
{
let a = 10;
}
console.log(a);
}
test(); // undefined

let 不会变量提升,会产生一个暂时性的死区(TDZ),导致报错。暂时性死区是指在 let 作用域之上的区域。

console.log(a);
let a = 11;
// ReferenceError: Cannot access 'a' before initialization

未声明就引用

var a = a;
console.log(a); // undefined

let b = b;
console.log(b); // ReferenceError: Cannot access 'b' before initialization

函数形参死区

使用 ES6 语法时,形参可能存在暂时性死区。

function test(x = y, y = 2) {
console.log(x, y); // ReferenceError: Cannot access 'y' before initialization
}
test();

修正方法,先声明 x,然后再赋值给 y

function test(x = 2, y = x) {
console.log(x, y); // 2 2
}
test();

上述代码类似于下面的例子。

let x = 2;
let y = x;
console.log(x, y); // 2 2

typeof

console.log(typeof a); // undefined

因为 typeofa 被声明但尚未初始化时不会报错。

console.log(typeof a); // ReferenceError: Cannot access 'a' before initialization
let a;

let 只能在当前的块级作用域下生效。

if (10) {
let a = 2;
}
console.log(a); // ReferenceError: a is not defined

循环

以下代码不会报错,但会导致无限循环,建议在单独的环境中测试执行,因为 node 是单线程的,这段代码会一直执行,不会停止。

for (; 1; ) {
let a = 2;
}
console.log(a);

形参

形参的括号内属于块级作用域的范畴。

for (let i = 0; i < 10; i++) {}
console.log(i); // ReferenceError: i is not defined

for (var i = 0; i < 10; i++) {}
console.log(i); // 10
var arr = [];

for (var i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i);
};
}

for (var i = 0; i < 10; i++) {
arr[i](); // 10 次输出 10
}

循环的作用域

for (let i = 0; i < 10; i++) {
let i = 'a';
console.log(i); // SyntaxError: Identifier 'i' has already been declared
}

let 严格区分作用域,而 var 会变量提升到父级,与父级的 let 冲突,相当于 a 重复定义。

if (1) {
let a = 1;
{
let a = 10;
console.log(a); // 10
}
}

变量 a 被新的声明覆盖,因此输出 10

if (1) {
let a = 1;
{
a = 10;
console.log(a); // 10
}
}

不建议在块级作用域声明函数

if (1) {
function a() {
// 不建议在块级作用域中声明函数
}
}

如果使用,建议用函数表达式

if (1) {
let abc = function a() {
// 不建议在块级作用域中声明函数
};
}

块级作用域没有返回值

虽然草案中有提出,但目前浏览器对这种写法的支持不一致。

do {
return '块';
} while (false);