JavaScript中 let 命令,const 命令

let 命令

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

基本用法

javascript 复制代码
{
    let a = 10;
    var b = 1;
}
a // ReferenceError: a is not defined.
b // 1{
    let a = 10;
    var b = 1;
}
a // ReferenceError: a is not defined.
b // 1

上面代码在代码块之中,分别用letvar声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。

for循环的计数器,就很合适使用let命令。

javascript 复制代码
var a = [];
for (let i = 0; i < 10; i++) {
    a[i] = function () {
        console.log(i);
    };
}
a[6](); // 6

上面代码中,变量ilet声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

javascript 复制代码
for (let i = 0; i < 3; i++) {
    let i = 'abc';
    console.log(i);
}
// abc
// abc
// abc

上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。

不允许重复声明

let不允许在相同作用域内,重复声明同一个变量。

javascript 复制代码
// 报错
function func() {
    let a = 10;
    var a = 1;
}
// 报错
function func() {
    let a = 10;
    let a = 1;
}

因此,不能在函数内部重新声明参数。

javascript 复制代码
function func(arg) {
    let arg;
}
func() // 报错
function func(arg) {
    {
        let arg;
    }
}
func() // 不报错

不存在变量提升

var命令会存在"变量提升"现象,即变量可以在声明之前使用,值为undefined。这种现象会让人觉得很奇怪,按照一般的逻辑,变量应该在声明语句之后才可以使用。

那么为了纠正(避免)这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则会报错。

javascript 复制代码
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

以上代码中,变量foovar命令声明,会发生变量提升,即脚本开始运行时,变量foo已经存在了,但是没有值,所以会输出undefined。变量barlet命令声明,不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这时如果用到它,就会抛出一个错误。

const 命令

const 声明一个只读的常量。一旦声明,常量的值就不能改变。const 声明一个只读的常量。一旦声明,常量的值就不能改变

基本用法

javascript 复制代码
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.

上面代码表明改变常量的值会报错。

const 声明的变量不得改变值,这意味着,const 一旦声明变量,就必须立即初始化,不能留到以后赋值。且只声明不赋值,也会报错。

javascript 复制代码
if (true) {
    const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined

这表明,const声明的变量也是只在它所在的代码块有效。

const的本质

const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const 只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

暂时性死区

只要块级作用域内存在let命令,它所声明的变量就"绑定"(binding)这个区域,不再受外部的影响。

javascript 复制代码
var tmp = 123;
if (true) {
    tmp = 'abc'; // ReferenceError
    let tmp;
}

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

ES6 明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为"暂时性死区"(temporal dead zone,简称 TDZ)。

javascript 复制代码
if (true) {
    // TDZ开始
    tmp = 'abc'; // ReferenceError
    console.log(tmp); // ReferenceError
    let tmp; // TDZ结束
    console.log(tmp); // undefined
    tmp = 123;
    console.log(tmp); // 123
}

上面代码中,在let命令声明变量tmp之前,都属于变量tmp的"死区"。

"暂时性死区"也意味着typeof不再是一个百分之百安全的操作。

javascript 复制代码
typeof x; // ReferenceError
let x;

上面代码中,变量x使用let命令声明,所以在声明之前,都属于x的"死区",只要用到该变量就会报错。因此,typeof运行时就会抛出一个ReferenceError

作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。

javascript 复制代码
typeof undeclared_variable // "undefined"

上面代码中,undeclared_variable是一个不存在的变量名,结果返回"undefined"。所以,在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。

有些"死区"比较隐蔽,不太容易发现。

javascript 复制代码
function bar(x = y, y = 2) {
    return [x, y];
}
bar(); // 报错

上面代码中,调用bar函数之所以报错(某些实现可能不报错),是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于"死区"。如果y的默认值是x,就不会报错,因为此时x已经声明了。

ES6 规定暂时性死区和letconst语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。

总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

var、let 与 const 的区别

  1. 作用域var声明的变量属于函数作用域,而letconst声明的变量属于块级作用域。
  2. 声明初始化varlet在声明的时候可以不进行初始化;而const在声明的时候必须初始化。
  3. 变量提升var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefinedletconst不存在变量提升,即它们所声明的变量一定要在声明后使用,否则会报错。
  4. 修改与重复声明var声明的变量可以重复声明,而在同一块级作用域,let变量不能重新声明,const常量不能修改(对象的属性和方法,数组的内容可以修改)。
  5. 暂存性死区var不存在暂时性死区;letconst存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
相关推荐
破浪前行·吴1 小时前
【初体验】【学习】Web Component
前端·javascript·css·学习·html
染指悲剧3 小时前
vue实现虚拟列表滚动
前端·javascript·vue.js
浩浩测试一下4 小时前
Web渗透测试之XSS跨站脚本之JS输出 以及 什么是闭合标签 一篇文章给你说明白
前端·javascript·安全·web安全·网络安全·html·系统安全
前端搬运工X5 小时前
Object.keys 的原生 JS 类型之困
javascript·typescript
肖老师xy6 小时前
h5使用better scroll实现左右列表联动
前端·javascript·html
一路向北North6 小时前
关于easyui select多选下拉框重置后多余显示了逗号
前端·javascript·easyui
Libby博仙6 小时前
.net core 为什么使用 null!
javascript·c#·asp.net·.netcore
一水鉴天6 小时前
为AI聊天工具添加一个知识系统 之26 资源存储库和资源管理器
前端·javascript·easyui
万物得其道者成6 小时前
在高德地图上加载3DTilesLayer图层模型/天地瓦片
前端·javascript·3d
你挚爱的强哥6 小时前
基于element UI el-dropdown打造表格操作列的“更多⌵”上下文关联菜单
javascript·vue.js·elementui