ES6基础:let和const

hushuo
发布于 2021-1-15 13:32
浏览
0收藏

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

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

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


上面代码中,变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量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


上面代码中,变量i是let声明的,当前的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;
}

ES6基础:let和const-鸿蒙开发者社区

再看看书中的例子:

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

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

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


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

小结
let 和 var一样可以声明变量
let 不存在变量提升
let 不允许重复声明
let 声明的变量存在“暂时性死区”
在这里插入图片描述

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

2.2 const不可修改

const PI = 3.1415;
PI // 3.1415

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


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

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


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

2.3 对于不可修改的疑惑
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,就会报错。

小结
const 和 let 一样可以声明变量
const 不存在变量提升
const 不允许重复声明
const 声明的变量存在“暂时性死区”
const 声明的变量的存储地址不可写
在这里插入图片描述

三、块级作用域
在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只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

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

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

4.1 单纯的 let 和 const 声明

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

// babel

var num = 1;
var str = 'str';


咦~,应该babel转换后变成了var,我们是在无用功吗?

4.2 块级作用域中的 const 和 let

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.3 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,在每次循环中都会有一个独立的作用域,他们互不影响。

到这里关于let和const就总结到这里次

收藏
回复
举报
回复
    相关推荐