作用域与变量声明详解
作用域
在函数执行前一刻,[[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
因为 typeof
在 a
被声明但尚未初始化时不会报错。
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);