一、let命令
1. 基本语法
ES6中新增加了let语法,用来声明变量。它的用法与var类似,但是只有在let命令所在的代码块内部才有效。
而for循环的计数器,就很适合let命令。
for (let i = 0; i < 5; i++) { console.log(i); }
上面的i只在for循环内部是有效的。不像我么平时定义的var,是在循环的外部,就像这样:
var i; for (i = 0; i < 5; i++) { console.log(i); }
由于我们从前会碰到很多的问题,而在ES6中,我们都可以直接使用let啦!我的建议是在for循环里一律使用let,防止不必要的麻烦和错误。
2. 不存在变量提升
let和var的区别在于,let必须要在声明后使用,否则就会报错。
console.log(foo); // 输出undefined console.log(bar); // 报错ReferenceError var foo = 2; let bar = 2;
其中输出undefined大家都清楚,是因为在解析的时候存在变量提升,所以运行的时候其实是var foo,在console.log(foo),所以由于未赋值,当然的就产生了undefined;而let不存在变量提升,所以自然地就报错了。
3. 暂时性死区
只要块级作用域内存在 let 命令, 它所声明的变量就“绑定”( binding) 这个区域, 不再受外部的影响。
var tmp = 123; if (true) { tmp = 'abc'; let tmp; }
上面代码中, 存在全局变量 tmp , 但是块级作用域内 let 又声明了一个局部变量 tmp , 导致后者绑定这个块级作用域, 所以在 let 声明变量前, 对 tmp 赋值会报错。
ES6明确规定, 如果区块中存在 let 和 const 命令, 这个区块对这些命令声明的变量, 从一开始就形成了封闭作用域。 凡是在声明之前就使用这些变量, 就会报错。
这样的设计是为了让大家养成良好的编程习惯, 变量一定要在声明之后使用, 否则就报错。
ES6规定暂时性死区和不存在变量提升, 主要是为了减少运行时错误, 防止在变量声明前就使用这个变量, 从而导致意料之外的行为。 这样的错误在ES5是很常见的, 现在有了这种规定, 避免此类错误就很容易了。
4. 不允许重复声明
let不允许在相同作用域内, 重复声明同一个变量。
// 报错 function () { let a = 10; var a = 1; }
二、块级作用域
1. 为什么需要块级作用域
ES5只有全局作用域和函数作用域, 没有块级作用域, 这带来很多不合理的场景。
(1) 第一种场景, 内层变量可能会覆盖外层变量。
var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = "hello world"; } }
f(); // undefined
为什么是undefined呢?其实不难理解,就是f()内部的tmp变量提升到了函数作用域的顶部,所以也就获取不到外部的tmp了。
(2) 第二种场景, 用来计数的循环变量泄露为全局变量
这正是我一开始谈到的var不是在for循环内部,而是在它的前面定义的,所以如果for循环是在全局作用域中,我们定义的 i 则会泄露成全局变量。
2. ES6的块级作用域
let 实际上为JavaScript新增了块级作用域。
function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
如果使用var定义呢?那么最后的输出结果将会是10。而使用let自动的将作用域绑定在了自己所在的作用域内部,我们可以看出来,嘿嘿,这东西多么好用了吧?
可能以后我们都将会抛弃var。
块级作用域的出现, 实际上使得获得广泛应用的立即执行匿名函数( IIFE) 不再必要了。
// IIFE写法 (function () { var tmp = ...; ... }()); // 块级作用域写法 { let tmp = ...; ... }
另外, ES6也规定, 函数本身的作用域, 在其所在的块级作用域之内。
三、 const命令
const 声明一个只读的常量。 一旦声明, 常量的值就不能改变。
const声明的变量不得改变值, 这意味着, const一旦声明变量, 就必须立即初始化, 不能留到以后赋值。
const的作用域与let命令相同: 只在声明所在的块级作用域内有效。
const命令声明的常量也是不提升, 同样存在暂时性死区, 只能在声明的位置后面使用。
const声明的常量, 也与 let 一样不可重复声明。
对于复合类型的变量, 变量名不指向数据, 而是指向数据所在的地址。 const命令只是保证变量名指向的地址不变, 并不保证该地址的数据不变, 所以将一个对象声明为常量必须非常小心
。
ES5只有两种声明变量的方法: var 命令和 function 命令。 ES6除了添加 let 和 const 命令, 后面章节还会提到, 另外两种声明变量的方法: import 命令和 class 命令。 所以, ES6一共有6种声明变量的方法。
四、 全局对象的属性
全局对象是最顶层的对象, 在浏览器环境指的是 window 对象, 在Node.js指的是 global 对象。 ES5之中, 全局对象的属性与全局变量是等价的。
未声明的全局变量, 自动成为全局对象 window 的属性, 这被认为是JavaScript语言最大的设计败笔之一。
ES6为了改变这一点, 一方面规定, 为了保持兼容性, var 命令和 function 命令声明的全局变量, 依旧是全局对象的属性; 另一方面规定, let 命令、 const 命令、 class 命令声明的全局变量, 不属于全局对象的属性。 也就是说, 从ES6开始, 全局变量将逐步与全局对象的属性脱钩。