# (一)let和const

变量声明是我们在学习一门语言时,最先了解的部分之一。不要忽略它,我们一起来看看ES6中新增的两种变量声明命令吧~

在开始ES6系列之初,我们就应该遵循尽量或者完全将它们应用到我们之后写的代码之中,加油!

# 1. let

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。即不会发生变量提升现象

如果大家对什么是变量提升有疑惑,不妨看看《JavaScript中的变量提升与预编译》 (opens new window)

# 1.1 let拒绝提升

console.log(value); // undefined
var value = '余光';

通过 var 声明的变量,声明的这个操作会被提前,你可以使用它。虽然它的值不一样符合你的预期。

{
  let a = 10;
  var b = 1;
}
console.log(a); // a is not defined
console.log(b); // 1

可以看到变量a只在他所在的 {}中有效,这样可以有效的减少全局变量污染的情况

# 1.2 经典的问题的for循环问题

还是那个问题,大家看下面的例子:

const arr = [];
for (var i = 0; i < 10; i++) {
    arr[i] = () => { console.log(i) }
}
arr[0](); // 10
arr[1](); // 10
arr[2](); // 10

上面代码中,变量ivar命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。

利用let的特点,我们改动一下代码:

const arr = [];
for (let i = 0; i < 10; i++) {
    arr[i] = () => { console.log(i) }
}
arr[0](); // 0
arr[1](); // 1
arr[2](); // 2

上面代码中,变量ilet声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,虽然输出的变量都叫i,但他们已经存在于不同的作用域之中了。

# 1.3 let不能在同一个代码块中重复声明

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

// 报错
function func() {
   let a = 10;
   var a = 1;
}

// 报错
function func() {
   let a = 10;
   let a = 1;
}

# 1.4 暂时性死区

大家不要被这个“高大上”的名字欺骗了

我们来看这段代码:

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError: Cannot access 'tmp' before initialization
  let tmp;
}

再看看书中的例子:

if (true) {
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

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

# 2.const

# 2.1 const不可修改

const绝大部分特点和let一样,但是它声明是一个只读的常量。一旦声明,常量的值就不能改变。

const PI = 3.1415;
PI // 3.1415

PI = 3; // TypeError: Assignment to constant variable.

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

const foo;
// SyntaxError: Missing initializer in const declaration

上面代码表示,对于const来说,只声明不赋值,会报错。

# 2.2 不可修改的到底是什么

const的不可写,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

对于基本类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。

但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

下面是另一个例子。

const a = [];
a.push('Hello'); // 可执行
a.length = 0;    // 可执行
a = ['Dave'];    // 报错

上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。

在这里插入图片描述

# 3.块级作用域

在let和const声明变量时,我们经常看到“当前代码块”或“函数块”的字样,那么我们如何对这个进行定义呢?

ES6的块级作用域——有大括号( {}或() ),如果没有大括号,JavaScript 引擎就认为不存在块级作用域。

那么在没有块级作用域时,会发生什么问题呢?

# 3.1 内层变量可能会覆盖外层变量

var params = { name: '余光' };

function func() {
    console.log(params);
    var params = {
        name: 10
    }
}

func(); // undefined

被var声明的变量存在极大的风险

# 3.2 用来计数的循环变量泄露为全局变量

又是那个熟悉的场景

var s = 'hello';

for (var i = 0; i < s.length; i++) {
  console.log(s[i]);
}

console.log(i); // 5

上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

# 4.思考

既然ES6是Js的新标准(现在已经不算是新标准了...),它的向后兼容是什么样的呢?

我们借助babel来看一下文章内提到的部分例子:

# 4.1 let 和 const 声明

let num = 1;
const str = 'str';

// babel
var num = 1;
var str = 'str';

咳咳,在没有作用域的情况下,看不出来什么,继续:

const bool = true;
if (bool) {
	  let a = 1;
}
console.log(a);

// babel
var bool = true; // 作用域外
if (bool) {
    var _a = 1; // 作用域内变量变为 唯一
}

console.log(a);

你会发现,a变量变成了 _a变量,这样你在作用域外使用它时,当然没有这个变量!

# 4.2 for 循环中的let

const arr = [];
for (let i = 0; i < 10; i++) {
    arr[i] = function() { console.log(i) }
}

// babel
var arr = [];
var _loop = function _loop(i) {
    arr[i] = function() {
        console.log(i);
    };
};

for (var i = 0; i < 10; i++) {
    _loop(i);
}

你会发现let声明i, 在每次循环中都会有一个独立的作用域,他们互不影响。他会通过传值的方式将变量 i独立出来

# 总结

  1. let 和 var 一样声明变量,不存在变量提升,不允许重复声明,块作用域内会产生暂时性死区
  2. const 和 let 一样声明变量,不存在变量提升,不允许重复声明,块作用域内会产生暂时性死区,声明的变量的存储地址不可写

在这里插入图片描述

参考:

# 写在最后

JavaScript系列:

  1. 《JavaScript内功进阶系列》(已完结) (opens new window)
  2. 《JavaScript专项系列》(持续更新) (opens new window)
  3. 《ES6基础系列》(持续更新) (opens new window)
  • 花名:余光(沉迷JS,虚心学习中)
  • WX:j565017805

其他沉淀

如果您看到了最后,对文章有任何建议,都可以在评论区留言

这是文章所在GitHub仓库的传送门 (opens new window),如果真的对您有所帮助,希望可以点个star,这是对我最大的鼓励 ~

Last Updated: 2/15/2023, 7:45:22 PM