Es6语法学习(一)

学习资料主要参考自阮一峰大佬的博客文章。ECMAScript 6 入门

let命令

let声明的变量只在它所在的代码块有效。

1
2
3
4
5
6
{
let a = 1
var b = 2
}
a // ReferenceError: a is not defined.
b // 1

变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。

1
2
3
4
5
6
7
8
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); //10
a[1](); //10

变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。

1
2
3
4
5
6
7
8
var a = [];
for (let i = 0; i < 10; i++) {
console.log(i)
a[i] = function () {
console.log(i);
};
}
a[6](); // 6

另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

1
2
3
4
5
6
7
 for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc

let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

1
2
3
4
5
6
7
//var
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
//console.log(bar); // 报错ReferenceError
let bar = 2;

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
只要代码块中存在let,就必须对变量值赋值,不然就会报错,并且需要在一开始声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (true) {
// TDZ开始
//tmp = 'abc'; // ReferenceError
// console.log(tmp); // ReferenceError
//犯了上面的错误,需要先声明再使用
let tmp; // TDZ结束
console.log(tmp); // undefined

tmp = 123;
console.log(tmp); // 123
}
//在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。
typeof x; // ReferenceError
let x;
typeof undeclared_variable // "undefined"
先声明,在调用
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错

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

1
2
3
4
5
6
7
8
9
10
11
function func(arg) {
let arg;
}
func() // 报错

function func(arg) {
{
let arg;
}
}
func() // 不报错

块级作用域

ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

第一种场景,内层变量可能会覆盖外层变量。

1
2
3
4
5
6
7
8
9
var tmp = new Date();

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

第二种场景,用来计数的循环变量泄露为全局变量。

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

const命令

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

1
2
3
4
5
6
7
8
9
10
11
12
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
//
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
//上面代码中, 常量a是一个数组, 这个数组本身是可写的, 但是如果将另一个数组赋值给a, 就会报错。

ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

1
2
3
4
5
6
7
8
9
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
/*上面代码中,全局变量a由var命令声明,所以它是顶层对象的属性;
全局变量b由let命令声明,所以它不是顶层对象的属性,返回undefined。
*/