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
4for (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
6var tmp = 123;
//不受作用域外部影响
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
(3)不允许重复声明1
2
3
4
5
6
7
8
9function 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
14const 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 | let [foo, [[bar], baz]] = [1, [[2], 3]]; |
3.2 对象的解构赋值
1 | let { foo, bar } = { foo: "aaa", bar: "bbb" }; |
3.3 字符串的解构赋值
1 | const [a, b, c, d, e] = 'hello'; |
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 | 'x'.repeat(3) // "xxx" |
4.3 padStart(),padEnd()
1 | //padStart的常见用途是为数值补全指定位数。下面代码生成10位的数值字符串。 |
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
24const 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 | //通常情况下,定义了默认值的参数,应该是函数的尾参数。 |
5.2 rest参数(形式为…变量名)
函数的length属性,不包括 rest 参数。1
2
3
4
5
6
7
8
9
10
11function 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
5var f = v => v;
//等同于
var f = function(v) {
return v;
};
箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
1 | //嵌套的箭头函数 |
5.4 尾调用优化
尾调用指某个函数的最后一步是调用另一个函数
“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。
如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。1
2
3function 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
14const [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
6Array.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
7let 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
2Array.from([1, , 2, , 3], (n) => n || 0)
// [1, 0, 2, 0, 3]
6.3 Array.of()
Array.of方法用于将一组值,转换为数组。1
2
3Array.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 | for (let index of ['a', 'b'].keys()) { |
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
7var 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
5class 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
16Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};
<3>克隆对象1
2
3function clone(origin) {
return Object.assign({}, origin);
}
<4>合并多个对象1
2const merge =
(target, ...sources) => Object.assign(target, ...sources);
<5>为属性指定默认值1
2
3
4
5
6
7
8
9
10const 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
8let 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…in1>
for…in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
<2>Object.keys(obj)2>
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)。
<3>Object.getOwnPropertyNames(obj)3>
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)。
<4>Object.getOwnPropertySymbols(obj)4>
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性。
<5>Reflect.ownKeys(obj)5>
Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管属性名是 Symbol 或字符串,也不管是否可枚举。
7.5 proto属性
无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的
Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。1
2
3
4
5
6
7
8
9
10let 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 | var obj = { foo: 'bar', baz: 42 }; |
7.7 Null 传导运算符
如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。
比如,要读取message.body.user.firstName,安全的写法是写成下面这样。1
2
3
4
5
6
7const 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
8var 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
4var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
s1 === s2 // true
Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key。1
2
3
4
5var s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
var s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
9.Set和Map
9.1 Set类似于数组,但是成员的值都是唯一的,没有重复的值。
1 | // 例一 |
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
9const 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(已失败)。1>
<2>一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var 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
3Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
10.4 应用于加载图片
1 | const preloadImage = function (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
13const 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
9var 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
7let 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 | var myIterable = {}; |
12.3 Generator.prototype.return()
1 | function* gen() { |
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
35function* 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
14function* 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
25var 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();1>
<2>更好的语义2>
<3>更广的适用性3>
<4>返回值是Promise4>
基本用法:1
2
3
4
5
6
7
8
9async 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
12function 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
12async 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
11class 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
10class 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属性,表示构造函数的继承,总是指向父类。1>
<2>子类prototype属性的proto属性,表示方法的继承,总是指向父类的prototype属性。1
2
3
4
5
6
7
8class 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
13function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false

