Skip to main content

ES6 let/const

let命令#

let、const的出现到底有什么作用呢?首先说说它们的作用,let、const替代var的使用,避免了内层可能会覆盖外层变量,减少bug的源头,let、const的规范使用也能够提升代码编译的效率。

块级作用域的出现#

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

{    let a = 1    var b = 2}console.log(a) // ReferenceError: a is not definedconsole.log(b) // 2

使用let声明a表示只有所在的代码块才会声明有效,因此会抛出 not defind 错误,在声明代码块之外拿a值会不存在。var 全局声明变量,因此返回为了正确的值2。

变量不会提升#

if(false){  var a = 21    let b = 1}console.log(a) // undefindconsole.log(b) // b is not defind

var不会产生块级作用域,let会产生块级作用域
相当于下面

var a if(fasle) {  a = 21  let b = 1}

暂时性死区#

var a = 1if(true) {    console.log(a)    let a = 21}

let声明的变量不会提升,并且会产生暂时性死区。
在let声明变量之前访问变量会抛出错误

重复声明会报错#

var value = 1let value = 2

会抛出错误:SyntaxError: Identifier 'value' has already been declared

不绑定全局作用域#

var value = 1let values = 2console.log(window.value) // 1console.log(window.values) // undefind

为什么需要块级作用域#

在ES5中只有全局作用域和函数作用域,没有块级作用域,带来很多不合理的场景,比如内层可能会覆盖外层变量

var tmp = new Date();
function f() {  console.log(tmp);  if (false) {    var tmp = 'hello world';  }}
f(); // undefined

外层定义了 tmp 在函数内层 f 又定义了 tmp,根据上文,tmp 会变量提升值,导致内层的tmp变量覆盖了外层的tmp变量。

再比如循环中的变量变为全局变量

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

for循环结束后并没有消失,前面说过var会绑定全局变量,所以循环后,console.log输出的i就是5了。

循环中的块级作用域#

var funcs = [];for (var i = 0; i < 3; i++) {    funcs[i] = function () {        console.log(i);    };}funcs[0](); // 3

改变方式让funcs[0](); 输出正确
解决方案:使用闭包保存变量
这里主要讲let、csont命令,闭包不会讲

var funcs = [];for (var i = 0; i < 3; i++) {    funcs[i] = function () {        return function () {            console.log(i);        }    };}funcs[0](); // 0

ES6 的 let 为这个问题提供了新的解决方法:

var funcs = [];for (let i = 0; i < 3; i++) {    funcs[i] = function () {        console.log(i);    };}funcs[0](); // 0

问题在于,上面讲了 let 不提升,不能重复声明,不能绑定全局作用域等等特性,可是为什么在这里就能正确打印出 i 值呢?

如果是不重复声明,在循环第二次的时候,又用 let 声明了 i,应该报错呀,就算因为某种原因,重复声明不报错,一遍一遍迭代,i 的值最终还是应该是 3 呀,还有人说 for 循环的 设置循环变量的那部分是一个单独的作用域,就比如:

for (let i = 0; i < 3; i++) {  let i = 'abc';  console.log(i);}// abc// abc// abc

我们把var改成let试试

for (var i = 0; i < 3; i++) {  var i = 'abc';  console.log(i);}// abc

那为什么输出结果不一样了呢,
如果要追究这个问题,就要抛弃掉之前所讲的这些特性!这是因为 let 声明在循环内部的行为是标准中专门定义的,不一定就与 let 的不提升特性有关,其实,在早期的 let 实现中就不包含这一行为。

for (let i = 0; i < 3; i++) {  let i = 'abc';  console.log(i);}// abc// abc// abc

简单的来说,就是在 for (let i = 0; i < 3; i++) 中,即圆括号之内建立一个隐藏的作用域,这就可以解释为什么

var funcs = [];for (let i = 0; i < 3; i++) {    funcs[i] = function () {        console.log(i);    };}funcs[0](); // 0

相当于:

(let i = 0) {    focus[0] = function () {        console.log(i)    }}(let i = 2) {    focus[2] = function () {        console.log(i)    }}(let i = 3) {    focus[3] = function () {        console.log(i)    }}

当执行函数的时候,根据词法作用域就可以找到正确的值,其实你也可以理解为 let 声明模仿了闭包的做法来简化循环过程。

Babel#

我们知道es6代码运行中,会自动编译转换为es5,看看let、const是被怎么转换的

let value = 1  ===>编译为 var value = 1 
if(false){  let value = 1}console.log(value)

babel很聪明,编译为

if(false){  let _value = 1}console.log(value)

在看看之前循环中letbabel编译:

var funcs = [];for (let i = 0; i < 10; i++) {    funcs[i] = function () {        console.log(i);    };}funcs[0](); // 0

巧妙的编译为:

var funcs = [];
var _loop = function _loop(i) {    funcs[i] = function () {        console.log(i);    };};
for (var i = 0; i < 10; i++) {    _loop(i);}funcs[0](); // 0

const命令#

定义一个只读的变量,一旦定义不可改变,同样,const也有let一样的块级作用域作用。

const value = 1const value = 2

Uncaught SyntaxError: Identifier 'value' has already been declared

本质#

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

const foo = {};
// 为 foo 添加一个属性,可以成功foo.prop = 123;foo.prop // 123
// 将 foo 指向另一个对象,就会报错foo = {}; // TypeError: "foo" is read-only

Es6声明变量共有六种方法#

var、function、let、const、import、class