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)
在看看之前循环中let的babel编译:
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