本文深入解析JavaScript中var、let和const三种变量声明方式的核心区别。重点阐述var缺乏块级作用域导致的闭包陷阱,以及let如何解决变量提升和循环共享问题。同时说明const定义常量的不可变性规则及其对对象引用的特殊行为,帮助开发者根据场景选择正确的声明方式。
JavaScript变量声明演进概述
随着ES6规范的发布,JavaScript中定义变量的方式已从单一的var扩展为var、let和const三种。虽然var广为人知,但新增的let和const具备哪些新特性?在何种场景下应如何选择?本文将对这三者的区别及用法进行详细总结。
var声明的局限性分析
首先探讨使用var声明变量时存在的潜在问题。
JavaScript传统上不具备块级作用域概念。
根据JS规则,在函数内部使用var声明的变量,其作用域覆盖整个函数体。
for(var i=0;i<10;i++){
var a = 'a';
}
console.log(a);
即使代码已跳出for循环,依然可以访问在循环内部定义的变量a,甚至连循环变量i也能被访问,这往往导致意外结果。
2. 循环内变量过度共享问题
请思考以下代码执行时的行为。
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i)
}, 1000);
}
在浏览器中运行该代码,观察结果是否与预期一致。
控制台输出了三个3,而非预期的0、1、2。
根本原因在于,循环体及三次setTimeout回调共享同一个变量i。当循环结束时,i的值变为3。因此,当第一个定时器触发时,读取到的i值即为3。
let声明的特性与优势
至此,let的作用已显而易见。
没错,let正是为了解决上述两个问题而引入。只需将前述示例中的var替换为let并重新运行,即可验证效果。
注意:必须在代码开头声明'use strict'后才能使用let,否则浏览器可能无法正确显示结果。
let可视为更完善的var。
let声明的变量具有块级作用域,即其作用域仅限于外层代码块,而非整个外层函数。let保留了变量提升特性,但不会像var那样盲目提升至函数顶部。在示例一中,将var替换为let即可快速修复问题;若全程使用let声明,可避免此类bug。
let声明的全局变量并非全局对象的属性。这意味着无法通过window.变量名的方式访问这些变量,它们仅存在于一个不可见的块作用域中,该块理论上涵盖Web页面中运行的所有JS代码的外层块。
形如for (let x...)的循环在每次迭代时都会为x创建新的绑定。
这是一个细微但重要的区别。以示例二为例,若for (let...)循环执行多次且循环内部保持闭包,则每个闭包将捕获循环变量的不同副本值,而非所有闭包共享同一个变量值。
因此,在示例二中,将var替换为let同样可以修复bug。
此特性适用于现有的三种循环方式:for-of、for-in以及传统的分号分隔类C循环。
使用let重新定义变量会抛出语法错误(SyntaxError)。
这一点易于理解,通过代码即可验证。
let a = 'a';
let a = 'b';
上述写法不被允许,浏览器会报错,原因在于重复定义了变量。
除上述差异外,let与var高度相似。例如,两者均支持使用逗号分隔声明多个变量,且都支持解构赋值特性。
const常量的定义与限制
ES6引入的第三个声明关键词:const。
简而言之,const用于定义常量!任何不符合常量语义的写法均属非法。
例如以下写法:
//只声明变量不赋值
const a
这样:
//重复声明变量
const a = 'a';
const a = 'b';
还有以下写法:
//给变量重新赋值
const a = 'a';
a = 'b'
再来看一种特殊情况:
//不过不推荐这么干,实在没啥意思,常量常量,不变的才叫常量嘛~
const a = {a:'a'};
//重新赋值当然是行不通的了
a = {a:'b'};
//嘿嘿嘿科技
a.a = 'b'
const本身并无复杂之处,普通用户完全可以使用。但对于高级用户而言,如何正确使用const仍可能带来挑战。
以上即为对var、let、const用法的总结,如有不当之处,欢迎指正。
如需了解更多关于JavaScript ES6规范的内容,请参阅以下相关文章。