ES6学习笔记(一)

声明:本篇blog部分内容摘自阮一峰老师的ECMAScript 6入门,全部内容参考自上述链接

声明变量的方法

var function let const import class
作用域 函数或全局 顶层、函数和块级作用域 所在代码块 所在代码块 - -
变量提升 存在 存在 不存在 不存在 存在 不存在
暂时性死区
重复声明 允许 允许 不允许 不允许 - 不允许
值可变 -
属于全局对象 -

作用域

可访问到变量的代码区块

1
2
3
4
5
6
7
{
let a = 10;
var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

变量提升

不存在变量提升的声明方式,变量一定要在声明后使用,否则会报错。

1
2
3
4
5
console.log(foo); // 输出undefined
console.log(bar); // 报错ReferenceError

var foo = 2;
let bar = 2;

暂时性死区

在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。

1
2
3
4
5
6
7
8
9
10
11
12
13
var tmp = '123';

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

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

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

重复声明

在同一个作用域内重复声明同一个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 报错
function () {
let a = 10;
var a = 1;
}

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

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

值可变

const声明的是一个只读的常量,声明时必须初始化,否则会报错。声明后常量的值不可改变。

对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const只保证变量名指向的地址不变,而该地址的数据可变。

1
2
3
4
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错

属于全局对象

声明的全局变量是否属于全局对象(浏览器环境window,Node中是指global)

ES6规定var和function声明的全局变量为全局对象的属性,而let、const、class声明的全局变量不属于全局对象的属性。

1
2
3
4
5
6
7
var a = 1;
// 如果在Node的REPL环境,可以写成global.a
// 或者采用通用方法,写成this.a
window.a // 1

let b = 1;
window.b // undefined

变量的解构赋值

概念

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

基本规则

数组:

1
2
3
4
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
var { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

var { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

var { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

var { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

字符串:

1
2
3
4
5
6
7
8
9
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

let {length : len} = 'hello';
len // 5

数值和布尔值:

1
2
3
4
5
let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

函数参数:

1
2
3
4
5
function add([x, y]){
return x + y;
}

add([1, 2]); // 3

数组与对象解构的区别

数组的解构,元素是按顺序进行解构的,而对象的解构看的是属性名,与顺序无关。

不完全解构

等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

以数组为例

1
2
3
4
5
6
7
8
let [x, y] = [1, 2, 3];
x // 1
y // 2

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

解构不成功

等号右边没有足够或对应的值供左边匹配

1
2
3
4
var [foo] = [];
var [bar, foo] = [1];

//两种情况foo都为undefined

默认值

ES6内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。

1
2
3
4
5
6
7
var [x = 1] = [undefined];
//或者var [x = 1] = [];
x // 1

var [x = 1] = [null];
//null不严格等于undefined
x // null

如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。

1
2
3
4
5
6
7
function f() {
console.log('aaa');
}

let [x = f()] = [1];

//因为x能取到值1,所以f()不会执行

报错的情况

如果等号右边是不可遍历的结构,则会报错。

1
2
3
4
5
6
7
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

上面的表达式都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口(前五个表达式),要么本身就不具备Iterator接口(最后一个表达式)。

事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。

圆括号问题

只要有可能导致解构的歧义,就不得使用圆括号。

否则会报错。

建议在模式中尽量不要用圆括号。

解构的用途

  • 交换变量的值([x, y] = [y, x];
  • 从函数返回多个值(return [1, 2, 3];
  • 定义函数参数(function f([x, y, z]) { ... }
  • 提取JSON数据(let { id, status, data: number } = jsonData;
  • 设置函数参数默认值(function f({a = true, b = 1}){...}
  • 遍历Map(for (let [key, value] of map){}
  • 输入模块的指定方法(const { SourceMapConsumer, SourceNode } = require("source-map");

参考

有关变量声明的还没看到class跟import,待更新…