变量声明
文章目录
使用 var 的函数作用域声明
使用 var 声明变量,该变量会被自动添加到最接近的上下文中。
在函数和 with 语句中,最接近的上下文就是函数的局部的上下文。
若变量初始化未经声明,将被自动添加到全局上下文中。
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
let result = add(10, 20); // 30
console.log(sum); // 报错:sum 在这里不是有效变量
在上述代码中,函数 add() 定义了一个局部变量 sum,用于保存加法操作的结果。该值作为函数值被返回,但变量 sum 在函数外部无法访问。
若省略上述代码中函数内部声明变量 sum 使用的关键字 var,那么变量 sum 在函数 add() 被调用后,将变为可访问变量。
function add(num1, num2) {
sum = num1 + num2;
return sum;
}
let result = add(10, 20); // 30
console.log(sum); // 30
上述代码中,变量 sum 被用加法操作的结果初始化时并没有使用关键字 var 声明。在调用 add() 之后,变量被添加到全局上下文中,在函数退出执行上下文栈后依然存在。
注:未经声明而初始化变量是 JavaScript 编程一个常见错误,在严格模式下,未经声明就初始化变量会报错。
提升(hoisting)
提升:var 声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前。
提升使在同一作用域使用变量时,不必考虑其是否已经声明。
示例代码:
var name = "huaqi";
// 等价于
var name;
name = "huaqi"
// ================
function fn1() {
var name = "huaqi"
}
// 等价于
function fn2() {
var name;
name = "huaqi";
}
通过在变量声明之前打印该变量,可以验证变量会被提升。声明的提升意味着会输出 undefined 而不是 Reference Error:
console.log(name); // undefined
var name = "huaqi";
function() {
console.log(name); // undefined
var name = "huaqi";
}
使用 let 的块级作用域声明
关键字 let 对应块级作用域。
块级作用域由最近的一对包含花括号 { } 界定。所以,if 语句块、while 语句块、function 语句块、单独块都是 let 声明变量的作用域。
if (true) {
let a;
}
console.log(a); // Reference Error: a 未定义
while(true) {
let b;
}
console.log(b); // Reference Error: b 未定义
function foo() {
let c;
}
console.log(c); // Reference Error: c 未定义
// 不是对象字面量,而是一个独立的块
// JavaScript 解释器会根据其中内容进行识别
{
let d;
}
console.log(d); // Reference Error: d 未定义
let 重复声明报错
重复的 var 声明会被忽略。
重复的 let 声明会抛出 Syntax Error。
循环中使用 let 声明迭代变量
使用 var 声明的迭代变量会泄露到循环外部,这种情况应该避免,而使用 let 声明迭代变量则没有这个问题。
for (var i = 0; i < 10; ++i) {}
console.log(i); // 10;
for (let j = 0; j < 10; ++j) {}
console.log(j); // Reference Error: j 未定义
暂时性死区(temporal dead zone)
严格来说,let 在 JavaScript 运行时也会被提升,但由于暂时性死区(temporal dead zone)的缘故,实际上不能在声明之前使用以关键字 let 声明的变量。
使用 const 的常量声明
使用 const 声明的变量必须同时初始为某个值。一经声明,在其生命周期的任何时候都不能再重新赋予新值。
const a; // Syntax Error: 常量声明没有初始化
const b = 3;
console.log(b); // 3
b = 4; // Type Error: 给常量赋值
const 除了要遵循以上规则,其他方面与 let 声明一样。
const 声明只应用于顶级原语或者对象。即赋值为对象的 const 变量不能再被重新赋值为其他引用值,但对象的键则不受限制。
const o1 = {};
o1 = {}; // Type Error: 给常量赋值
const o2 = {};
o2.name = "huaqi";
console.log(o2.name); // "huaqi"
Object.freeze() 整个对象不被修改
可以使用 Obejct.freeze() 确保对象的属性不再被重新赋值,但会静默失败。
const o3 = Obejct.freeze({});
o3.name = "name";
console.log(o3.name); // undefined
尽可能多的使用 const 声明
由于 const 声明暗示变量的值是单一类型且不可修改,JavaScript 运行时编译器可以将其所有实例都替换为实际的值,而不会通过查询表进行变量查询。Chrome 的 V8 引擎执行上述优化
开发实践表明,如果开发流程并不会因此而受很大影响,就应该尽可能多的使用 const 声明。
标识符查找
当在特定上下文中为读取或写入而引用一个标识符时,必须通过搜索确定这个标识符表示什么。搜索开始于作用域链前端,以给定的名称搜索对应的标识符。
若在局部上下文中寻找到该标识符,则搜索停止,变量确定;若没有寻找该变量名,则继续沿作用域链搜索。(作用域链中的对象也有一个原型链,因此搜索可能涉及每个对象的原型链)。
上述过程会一直持续到搜索至全局上下文的变量对象。如果仍然没有找到标识符,则说明其未声明。
var color = "blue";
function getColor() {
return color;
}
console.log(getColor()); // "blue"
上述代码中,调用函数 getColor() 时会引用变量 color。为确定 color 的值会进行两步搜索。
- 搜索 getColor() 的变量对象,查找名为 color 的标识符。
- 未找到
- 继续搜索作用域链中下一个作用域的变量对象(即全局上下文的全局对象)
- 找到名为 color 的标识符
- 搜索结束
使用块级作用域声明并不会改变搜索流程 ,但可以给词法层级添加额外的层次
var color = "blue";
function getColor() {
let color = "red";
{
let color = "green";
return color;
}
}
console.log(getColor()); // "green"
在局部变量 color 声明之后的任何代码都无法全局访问变量 color,除非使用完全的限定的写法 window.color。