若尧

ES6入门

总结于:ECMAScript6入门

1 Babel转码器

Babel是一个广泛使用的ES6转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。

1
2
3
4
5
6
7
// 转码前
input.map(item => item + 1);

// 转码后
input.map(function (item) {
return item + 1;
});

2 let和const

2.1 let命令

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

for循环的计数器,就很合适使用let命令。

1
2
3
4
for (let i = 0; i < 10; i++) {}

console.log(i);
//ReferenceError: i is not defined

注:(1)let不存在变量提升

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

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

(2)暂时性死区

1
2
3
4
5
6
var tmp = 123;
//不受作用域外部影响
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}

(3)不允许重复声明

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

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

(4)let实际上为 JavaScript 新增了块级作用域。
块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。

1
2
3
4
5
6
7
8
9
10
11
// IIFE 写法
(function () {
var tmp = ...;
...
}());

// 块级作用域写法
{
let tmp = ...;
...
}

2.2 const命令

const声明一个只读的常量。一旦声明,常量的值就不能改变。
const的作用域与let命令相同:只在声明所在的块级作用域内有效。
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

//如果真的想将对象冻结,应该使用Object.freeze方法。
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;

3 变量的解构赋值

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

3.1 数组的解构赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

3.2 对象的解构赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

let foo;
let {foo} = {foo: 1}; // SyntaxError: Duplicate declaration "foo"

//下面代码中,let命令下面一行的圆括号是必须的,否则会报错。
let foo;
({foo} = {foo: 1}); // 成功

//嵌套
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

3.3 字符串的解构赋值

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

//对length属性解构赋值
let {length : len} = 'hello';
len // 5

4 字符串的扩展

4.1 includes(), startsWith(), endsWith()

includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。

1
2
3
4
5
6
7
8
//这三个方法都支持第二个参数,表示开始搜索的位置。
var s = 'Hello world!';

s.includes('o') // true

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

4.2 repeat() 表示将原字符串重复n次。

1
2
3
4
5
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
'na'.repeat(NaN) // ""
'na'.repeat(-1)// RangeError

4.3 padStart(),padEnd()

1
2
3
4
5
6
7
8
//padStart的常见用途是为数值补全指定位数。下面代码生成10位的数值字符串。
'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"

//另一个用途是提示字符串格式。
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

4.4 模板字符串

模板字符串(template string)是增强版的字符串,用反引号(`)标识。
它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

1
2
3
4
5
6
7
8
9
10
11
12
// 字符串中嵌入变量
var name = "Bob", time = "today";
`Hello ${name},
how are you ${time}?`

var obj = {x: 1, y: 2};
`${obj.x + obj.y}`// 3

function fn() {
return "Hello World";
}
`foo ${fn()} bar`// foo Hello World bar

模板字符串甚至还能嵌套。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const tmpl = addrs => `
<table>
${addrs.map(addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`;
//使用方法
const data = [
{ first: '<Jane>', last: 'Bond' },
{ first: 'Lars', last: '<Croft>' },
];

console.log(tmpl(data));
// <table>
//
// <tr><td><Jane></td></tr>
// <tr><td>Bond</td></tr>
//
// <tr><td>Lars</td></tr>
// <tr><td><Croft></td></tr>
//
// </table>

如果需要引用模板字符串本身,在需要时执行,可以像下面这样写。

1
2
3
4
5
6
7
8
9
// 写法一
let str = 'return ' + '`Hello ${name}!`';
let func = new Function('name', str);
func('Jack') // "Hello Jack!"

// 写法二
let str = '(name) => `Hello ${name}!`';
let func = eval.call(null, str);
func('Jack') // "Hello Jack!"

5.函数的扩展

5.1 函数的默认值

1
2
3
4
5
6
7
8
9
//通常情况下,定义了默认值的参数,应该是函数的尾参数。
function log(x, y = 'World') {
console.log(x, y);
}

log('Hello') // Hello World

//函数的 length 属性:将返回没有指定默认值的参数个数。
log.length //1

5.2 rest参数(形式为…变量名)

函数的length属性,不包括 rest 参数。

1
2
3
4
5
6
7
8
9
10
11
function add(...values) {
let sum = 0;

for (var val of values) {
sum += val;
}

return sum;
}

add(2, 5, 3) // 10

5.3 箭头函数

箭头左边是参数,右边是返回值

1
2
3
4
5
var f = v => v;
//等同于
var f = function(v) {
return v;
};

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//嵌套的箭头函数
function insert(value) {
return {into: function (array) {
return {after: function (afterValue) {
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}};
}};
}

insert(2).into([1, 3]).after(1); //[1, 2, 3]

//等同于
let insert = (value) => ({into: (array) => ({after: (afterValue) => {
array.splice(array.indexOf(afterValue) + 1, 0, value);
return array;
}})});

insert(2).into([1, 3]).after(1); //[1, 2, 3]

5.4 尾调用优化

尾调用指某个函数的最后一步是调用另一个函数
“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。
如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。

1
2
3
function f(x){
return g(x);
}

尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。
但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

6.数组的扩展

6.1 扩展运算符(…)

该运算符主要用于函数调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
// ES5 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f(...args);

另一个例子是通过push函数,将一个数组添加到另一个数组的尾部。(可用于翻页)

1
2
3
4
5
6
7
8
9
// ES5的 写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

扩展运算符的应用:

<1>合并数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more]

var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];

// ES5的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

<2>与解构赋值结合
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest // []

const [first, ...rest] = ["foo"];
first // "foo"
rest // []

const [...butLast, last] = [1, 2, 3, 4, 5];
// 报错

<3>字符串
扩展运算符还可以将字符串转为真正的数组。
能够正确识别32位的Unicode字符。
正确返回字符串长度的函数

1
2
3
4
5
6
7
8
[...'hello']
// [ "h", "e", "l", "l", "o" ]

function length(str) {
return [...str].length;
}

length('x\uD83D\uDE80y') // 3

6.2 Array.from()

Array.from方法用于将两类对象转为真正的数组:
类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。

1
2
3
4
5
6
7
8
9
10
11
// NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function (p) {
console.log(p);
});

// arguments对象
function foo() {
var args = Array.from(arguments);
// ...
}

扩展运算符(…)也可以将某些数据结构转为数组。
区别在于:任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。

Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

1
2
3
4
5
6
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]

下面的例子是取出一组DOM节点的文本内容。

1
2
3
4
5
6
7
let spans = document.querySelectorAll('span.name');

// map()
let names1 = Array.prototype.map.call(spans, s => s.textContent);

// Array.from()
let names2 = Array.from(spans, s => s.textContent)

下面的例子将数组中布尔值为false的成员转为0。

1
2
Array.from([1, , 2, , 3], (n) => n || 0)
// [1, 0, 2, 0, 3]

6.3 Array.of()

Array.of方法用于将一组值,转换为数组。

1
2
3
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

6.4 数组实例的 find() 和 findIndex()

数组实例的find方法,用于找出第一个符合条件的数组成员。

1
2
3
4
5
6
7
8
9
10
[1, 4, -5, 10].find((n) => n < 0)
// -5

[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10

[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2

6.5 数组实例的fill()

fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

1
2
3
4
5
['a', 'b', 'c'].fill(7)
// [7, 7, 7]

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']

6.6 数组实例的 entries(),keys() 和 values()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"

7.对象的扩展

7.1 Object.is()

与严格比较运算符(===)的行为基本一致。
不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

1
2
3
4
5
+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

7.2 Object.assign()

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
由于undefined和null无法转成对象,所以如果它们作为参数,就会报错

1
2
3
4
5
6
7
var target = { a: 1 };

var source1 = { b: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
Object.assign方法实行的是浅拷贝,而不是深拷贝。

Object.assign方法有很多用处。

<1>为对象添加属性

1
2
3
4
5
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}

<2>为对象添加方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});

// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};

<3>克隆对象

1
2
3
function clone(origin) {
return Object.assign({}, origin);
}

<4>合并多个对象

1
2
const merge =
(target, ...sources) => Object.assign(target, ...sources);

<5>为属性指定默认值

1
2
3
4
5
6
7
8
9
10
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};

function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
console.log(options);
// ...
}

7.3 属性的可枚举性

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。
Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。

1
2
3
4
5
6
7
8
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true, 可枚举性
// configurable: true
// }

ES5 有三个操作会忽略enumerable为false的属性。
for…in循环:只遍历对象自身的和继承的可枚举的属性
Object.keys():返回对象自身的所有可枚举的属性的键名
JSON.stringify():只串行化对象自身的可枚举的属性
ES6 新增了一个操作Object.assign(),会忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
尽量不要用for…in循环,而用Object.keys()代替。

7.4 属性的遍历

ES6 一共有5种方法可以遍历对象的属性。

<1>for…in

for…in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

<2>Object.keys(obj)

Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)。

<3>Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)。

<4>Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性。

<5>Reflect.ownKeys(obj)

Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管属性名是 Symbol 或字符串,也不管是否可枚举。

7.5 proto属性

无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的
Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。

1
2
3
4
5
6
7
8
9
10
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);

proto.y = 20;
proto.z = 40;

obj.x // 10
obj.y // 20
obj.z // 40

7.6 Object.keys(),Object.values(),Object.entries()

1
2
3
4
5
6
7
8
9
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

Object.values(obj)
// ["bar", 42]

Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

7.7 Null 传导运算符

如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。
比如,要读取message.body.user.firstName,安全的写法是写成下面这样。

1
2
3
4
5
6
7
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';

//这样的层层判断非常麻烦,因此现在有一个提案,引入了“Null 传导运算符”
const firstName = message?.body?.user?.firstName || 'default';

8.Symbol

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。
它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

1
2
3
4
5
6
7
8
var s1 = Symbol('foo');
var s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

Symbol.for():重新使用同一个Symbol值

1
2
3
4
var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');

s1 === s2 // true

Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。

Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key。

1
2
3
4
5
var s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

var s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

9.Set和Map

9.1 Set类似于数组,但是成员的值都是唯一的,没有重复的值。

1
2
3
4
5
6
7
8
9
10
11
12
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]

// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5

// 去除数组的重复成员
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];// [3, 5, 2]

Set的操作方法:
add(value):添加某个值,返回Set结构本身。
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
has(value):返回一个布尔值,表示该值是否为Set的成员。
clear():清除所有成员,没有返回值。

Set的遍历方法:
keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
forEach():使用回调函数遍历每个成员

9.2 WeakSet:

结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

9.3 Map

Map类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

1
2
3
4
5
6
7
8
9
const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

9.4 WeakMap

WeakMap与Map的区别有两点。
首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。

10.Promise

10.1 Promise对象有以下两个特点:

<1>对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失败)。

<2>一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var promise = new Promise(function(resolve, reject) {
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

//Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});

10.2 Promise.all()

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

1
2
3
4
5
6
7
8
9
10
// 生成一个Promise对象的数组
var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});

Promise.race类似Promise.all,区别在于Promise.race有一个实例率先改变状态,p的状态就跟着改变,Promise.all而是要所有实例都改变状态。

10.3 Promise.resolve

作用是将现有对象转化为Promise对象

1
2
3
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

10.4 应用于加载图片

1
2
3
4
5
6
7
8
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
var image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};

11.Iterator

11.1 Iterator 的作用有三个:

一是为各种数据结构,提供一个统一的、简便的访问接口;
二是使得数据结构的成员能够按某种次序排列;
三是ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费。

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历

1
2
3
4
5
6
7
8
//数组的Symbol.iterator属性
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

原生具备 Iterator 接口的数据结构如下。
Array
Map
Set
String
TypedArray
函数的 arguments 对象

11.2 for…of循环

一个数据结构只要部署了Symbol.iterator属性,就被视为具有iterator接口,就可以用for…of循环遍历它的成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
const arr = ['red', 'green', 'blue'];

for(let v of arr) {
console.log(v); // red green blue
}

//空对象obj部署了数组arr的Symbol.iterator属性
const obj = {};
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);

for(let v of obj) {
console.log(v); // red green blue
}

for…in循环读取键名,for…of循环读取键值。

1
2
3
4
5
6
7
8
9
var arr = ['a', 'b', 'c', 'd'];

for (let a in arr) {
console.log(a); // 0 1 2 3
}

for (let a of arr) {
console.log(a); // a b c d
}

计算生成的数据结构 entries(),keys(),values()

1
2
3
4
5
6
7
let arr = ['a', 'b', 'c'];
for (let pair of arr.entries()) {
console.log(pair);
}
// [0, 'a']
// [1, 'b']
// [2, 'c']

与其他遍历语法的比较
for:最原始的写法,较为麻烦
forEach:数组内置的方法,无法中途跳出forEach循环,break命令或return命令都不能奏效。
for…in:主要用于遍历对象,不适合遍历数组,for…in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
某些情况下,for…in循环会以任意顺序遍历键名。

12.Generator

12.1 Generator 函数是一个状态机,封装了多个内部状态。

形式上,Generator 函数是一个普通函数,但是有两个特征。
一是,function关键字与函数名之间有一个星号;
二是,函数体内部使用yield表达式,定义不同的内部状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//该函数有三个状态:hello,world 和 return 语句(结束执行)
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
//调用 Generator 函数后,该函数并不执行,返回的是遍历器对象
var hw = helloWorldGenerator();

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

12.2 与 Iterator 接口的关系

1
2
3
4
5
6
7
8
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};

[...myIterable] // [1, 2, 3]

12.3 Generator.prototype.return()

1
2
3
4
5
6
7
8
9
10
11
function* gen() {
yield 1;
yield 2;
yield 3;
}

var g = gen();

g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }

12.4 yield* 表达式

用来在一个 Generator 函数里面执行另一个 Generator 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function* foo() {
yield 'a';
yield 'b';
}

function* bar() {
yield 'x';
yield* foo();
yield 'y';
}

// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}

// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}

for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"

12.5 应用

通过 Generator 函数部署 Ajax 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
//makeAjaxCall函数中的next方法,必须加上response参数,因为yield表达式,本身是没有值的
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}

var it = main();
it.next();

控制流管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//多步操作
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});

//采用 Promise 改写上面的代码。
Promise.resolve(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.done();

//Generator 函数可以进一步改善代码运行流程。(同步操作)
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}

13.async函数

async函数是Generator 函数的语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var fs = require('fs');

var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) reject(error);
resolve(data);
});
});
};

var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

//写成async函数,就是下面这样。
var asyncReadFile = async function () {
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

async函数对 Generator 函数的改进,体现在以下四点:

<1>内置执行器
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。
var result = asyncReadFile();

<2>更好的语义

<3>更广的适用性

<4>返回值是Promise

基本用法:

1
2
3
4
5
6
7
8
9
async function getStockPriceByName(name) {
var symbol = await getStockSymbol(name);
var stockPrice = await getStockPrice(symbol);
return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
console.log(result);
});

实例:按顺序完成异步操作:

<1>Promise 的写法如下。(这种写法不太直观,可读性比较差。)

1
2
3
4
5
6
7
8
9
10
11
12
function logInOrder(urls) {
// 远程读取所有URL
const textPromises = urls.map(url => {
return fetch(url).then(response => response.text());
});

// 按次序输出
textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise)
.then(text => console.log(text));
}, Promise.resolve());
}

<2>async 函数实现

1
2
3
4
5
6
7
8
9
10
11
12
async function logInOrder(urls) {
// 并发读取远程URL
const textPromises = urls.map(async url => {
const response = await fetch(url);
return response.text();
});

// 按次序输出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}

14.Class

14.1 简介

class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//生成实例对象的传统方法是通过构造函数
function Point(x, y) {
this.x = x;
this.y = y;
}

Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

//ES6写法
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
//类的所有方法都定义在类的prototype属性上面
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}

14.2 Class 的静态方法

加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用

1
2
3
4
5
6
7
8
9
10
11
class Foo {
static classMethod() {
return 'hello';
}
}

Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function

父类的静态方法,可以被子类继承。

14.3 Class的继承

子类必须在constructor方法中调用super方法,否则新建实例时会报错。

1
2
3
4
5
6
7
8
9
10
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}

toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}

<1>子类的proto属性,表示构造函数的继承,总是指向父类。

<2>子类prototype属性的proto属性,表示方法的继承,总是指向父类的prototype属性。

1
2
3
4
5
6
7
8
class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

15.Decorator

修饰器(Decorator)是一个函数,用来修改类的行为。
修饰器本质就是编译时执行的函数。

1
2
3
4
5
6
7
8
9
10
@testable
class MyTestableClass {
// ...
}

function testable(target) {
target.isTestable = true;
}

MyTestableClass.isTestable // true

如果觉得一个参数不够用,可以在修饰器外面再封装一层函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

打赏