ES6中let和const的使用方法详解
本文深入解析ES6中let和const命令的使用方法。重点讲解let的块级作用域、变量提升限制及暂时性死区特性,以及const声明常量时的内存指向规则。通过对比var,阐明ES6如何解决变量覆盖和循环计数泄露问题,并提供实际代码示例说明两种命令的使用规范与常见错误。
本文介绍ES6中let和const命令的具体用法,供读者参考学习。
let命令与块级作用域
ES6引入了let命令用于声明变量,其基本用法与var相似,但关键区别在于:let声明的变量仅在当前代码块内有效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
在代码块内外分别使用let和var声明变量并调用,结果显示:let声明的变量在块外调用会报错,而var声明的变量能正常返回数值。这证实了let声明的变量具有块级作用域限制。
for循环的计数器非常适合使用let命令进行声明。
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i);
上述代码中,计数器i仅在for循环内部有效,若在循环外部引用该变量,程序将报错。
//下面的代码如果使用var,最后输出的是10。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
若变量i由var命令声明,则其在全局范围内有效,导致全局仅存在一个i变量。每次循环中i的值都会更新,而数组a中赋值的函数内部console.log(i)所指向的正是这个全局i。因此,数组a所有成员中的i都指向同一个全局变量,最终输出最后一轮循环的i值,即10。
若改用let声明,变量仅在块级作用域内有效,最终输出结果为6。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
当变量i由let声明时,当前轮次的i仅在本轮循环中有效。这意味着每次循环声明的i都是新变量,因此最终输出为6。读者可能会疑惑:若每轮循环都重新声明i,如何获取上一轮的值以计算本轮?这是因为JavaScript引擎会记录上一轮循环的值,并在初始化本轮i时基于该值进行计算。
变量提升与暂时性死区
不存在变量提升现象
var命令存在“变量提升”现象,即变量可在声明前使用,此时值为undefined。这一现象略显反常,因为按常规逻辑,变量应在声明语句执行后才可使用。
为修正此现象,let命令改变了语法行为,规定所声明的变量必须在声明之后使用,否则将抛出错误。
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;
上述代码中,变量foo由var声明,会发生变量提升,脚本运行时变量已存在但无值,因此输出undefined。而变量bar由let声明,不会发生变量提升,表明在声明前该变量不存在,此时引用会抛出错误。
ES6明确规定,若代码块中存在let或const命令,则该区块对这些变量从一开始就形成封闭作用域。在声明前使用这些变量,程序将报错。
综上,在代码块内使用let命令声明变量之前,该变量均不可用。语法上称之为“暂时性死区”(temporal dead zone,简称TDZ)。
“暂时性死区”也意味着typeof运算符不再绝对安全。
typeof x; // ReferenceError
let x;
上述代码中,变量x由let声明,因此在声明前属于x的“死区”,只要引用该变量就会报错。因此,typeof运算时会抛出ReferenceError。
作为对比,若变量从未被声明,使用typeof反而不会报错。
typeof undeclared_variable // "undefined"
上述代码中,undeclared_variable是一个不存在的变量名,结果返回“undefined”。因此在引入let之前,typeof运算符是绝对安全的,绝不会报错。现在这一情况已不成立。此设计旨在培养良好的编程习惯,要求变量必须在声明后使用,否则将报错。
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错
上述代码中,调用bar函数之所以报错(部分实现可能不报错),是因为参数x的默认值依赖于另一个参数y,而此时y尚未声明,属于“死区”。若y的默认值为x,则不会报错,因为此时x已声明。
// 不报错
var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined
重复声明限制
不允许重复声明
let不允许在相同作用域内重复声明同一变量。
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
因此,在函数内部不得重新声明参数。
function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错
}
}
块级作用域的应用场景
2.块级作用域
为什么需要块级作用域?
ES5仅存在全局作用域和函数作用域,缺乏块级作用域,这导致诸多不合理场景。
第一种场景:内层变量可能覆盖外层变量。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
上述代码原意是:if代码块外部使用外层tmp变量,内部使用内层tmp变量。但函数f执行后输出结果为undefined,原因在于变量提升导致内层tmp变量覆盖了外层tmp变量。
第二种场景:用于计数的循环变量泄露为全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上述代码中,变量i仅用于控制循环,但循环结束后并未消失,而是泄露成为全局变量。
ES6的块级作用域
let实际上为JavaScript新增了块级作用域。
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
上述函数包含两个代码块,均声明了变量n,运行后输出5。这表明外层代码块不受内层代码块影响。若两次均使用var定义变量n,最终输出值将为10。
ES6允许块级作用域的任意嵌套。
{{{{{let insane = 'Hello World'}}}}};
上述代码使用了一个五层的块级作用域。外层作用域无法读取内层作用域的变量。
{{{{
{let insane = 'Hello World'}
console.log(insane); // 报错
}}}};
内层作用域可以定义与外层作用域同名的变量。
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};
块级作用域与函数声明
函数能否在块级作用域中声明?这是一个极易混淆的问题。
ES5规定,函数只能在顶层作用域和函数作用域中声明,不能在块级作用域中声明。
// 情况一
if (true) {
function f() {}
}
// 情况二
try {
function f() {}
} catch(e) {
// ...
}
上述两种函数声明,根据ES5的规定均属非法。
然而,浏览器未遵守此规定,为兼容旧代码,仍支持在块级作用域中声明函数,因此上述两种情况实际均可运行,不会报错。
const命令与常量本质
3.const命令
基本用法
const用于声明只读常量。一旦声明,常量的值不可更改。
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.
上述代码表明,尝试改变常量值将导致报错。
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不允许留待后续赋值。
const foo;
// SyntaxError: Missing initializer in const declaration
上述代码表示,对于const而言,仅声明而不赋值将导致报错。
const的作用域与let命令相同:仅在声明所在的块级作用域内有效。
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
const命令声明的常量同样不存在变量提升,且存在暂时性死区,只能在声明位置之后使用。
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
上述代码在常量MAX声明前即调用,结果报错。
const声明的常量,与let一样不可重复声明。
var message = "Hello!";
let age = 25;
// 以下两行都会报错
const message = "Goodbye!";
const age = 30;
本质
const实际上保证的并非变量值不可改动,而是变量指向的内存地址所保存的数据不可改动。对于简单类型数据(数值、字符串、布尔值),值直接保存在变量指向的内存地址,因此等同于常量。但对于复合类型数据(主要是对象和数组),变量指向的内存地址保存的仅是指向实际数据的指针,const仅保证该指针固定(即始终指向另一固定地址),至于其指向的数据结构是否可变,则完全无法控制。因此,将对象声明为常量时必须格外谨慎。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
上述代码中,常量foo存储的是一个地址,该地址指向一个对象。不可变的是该地址,即不能将foo指向另一个地址,但对象本身可变,因此仍可为其添加新属性。
ES6声明变量的六种方法
4.ES6声明变量的六种方法
ES5仅有两种声明变量的方法:var命令和function命令。ES6除了添加let和const命令,后续章节还将提及另外两种声明变量的方法:import命令和class命令。因此,ES6共有6种声明变量的方法。
顶层对象的属性
顶层对象在浏览器环境中指window对象,在Node环境中指global对象。ES5中,顶层对象的属性与全局变量等价。
window.a = 1;
a // 1
a = 2;
window.a // 2
上述代码中,顶层对象的属性赋值与全局变量的赋值属于同一件事。
感兴趣的朋友可使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容可查看本站专题:《JavaScript操作DOM技巧总结》、《JavaScript页面元素操作技巧总结》、《JavaScript事件相关操作与技巧大全》、《JavaScript查找算法技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript错误与调试技巧总结》
希望本文所述对大家JavaScript程序设计有所帮助。
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09
JS使用constructor判断数据类型可靠吗
2026-11-05 13:49:41
