es6,es7新特性总结(二)

简介: ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。ES6给我们提供了很多js的新特性和规范,使得我们编写Js代码更加灵活和强大,接下来让我们来学习一下吧。

数组的扩展


1.扩展运算符


将一个数组转为用逗号分隔的参数序列。

如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

替代函数的 apply 方法

// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);

复制数组

const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;

合并数组

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]

与解构赋值结合

// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list

字符串


JavaScript 会将四个字节的 Unicode 字符,识别为 2 个字符,采用扩展运算符就没有这个问题

凡是涉及到操作四个字节的 Unicode 字符的函数,最好都用扩展运算符改写。

[...'hello']
// [ "h", "e", "l", "l", "o" ]

Array.from()


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

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
// NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
  return p.textContent.length > 100;
});
// arguments对象
function foo() {
  var args = Array.from(arguments);
  // ...
}

扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。Array.from方法还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。


// Array.from还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);
// 取出一组 DOM 节点的文本内容
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
Array.from([1, , 2, , 3], (n) => n || 0)
// [1, 0, 2, 0, 3]

如果map函数里面用到了this关键字,还可以传入Array.from的第三个参数,用来绑定this。

// Array.from()可以将各种值转为真正的数组,并且还提供map功能。这实际上意味着,只要有一个原始的数据结构,你就可以先对它的值进行处理,然后转成规范的数组结构,进而就可以使用数量众多的数组方法。
Array.from({ length: 2 }, () => 'jack')
// ['jack', 'jack']

Array.of()


用于将一组值转化为数组

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
// 可以用以下方法模拟
function ArrayOf(){
  return [].slice.call(arguments);
}

数组实例的 copyWithin()


在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组

// 参数说明
// target(必需):从该位置开始替换数据。如果为负值,表示倒数。
// start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
// end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]

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


数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined

数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

这两个方法都可以接受第二个参数,用来绑定回调函数的this对象


// find
[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10
// findIndex
[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2
// 第二个参数
function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26
// indexOf方法无法识别数组的NaN成员,但是findIndex方法可以借助Object.is方法做到
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0

数组实例的 fill()


ill方法使用给定值,填充一个数组

['a', 'b', 'c'].fill(7)
// [7, 7, 7]
// fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
// 注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]

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


ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历


// 如果不使用for...of循环,可以手动调用遍历器对象的next方法,进行遍历
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']

数组实例的 includes()


返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true
// 该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true
// 兼容写法
const contains = (() =>
  Array.prototype.includes
    ? (arr, value) => arr.includes(value)
    : (arr, value) => arr.some(el => el === value)
)();
contains(['foo', 'bar'], 'baz'); // => false

数组的空位


数组的空位指,数组的某一个位置没有任何值,ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位,ES6 则是明确将空位转为undefined


对象扩展


属性的赋值器与取值器的简洁写法


简洁写法的属性名总是字符串,所以说属性中有关键字也不会报错


const cart = {
  _wheels: 4,
  get wheels () {
    return this._wheels;
  },
  set wheels (value) {
    if (value < this._wheels) {
      throw new Error('数值太小了!');
    }
    this._wheels = value;
  }
}
//如果是个遍历器,前面需要加一个星号
const obj = {
  * m() {
    yield 'hello world';
  }
};

Object.is()

用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。

不同之处只有两个:一是+0不等于-0,二是NaN等于自身

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

Object.assign()


用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

如果只有一个参数,Object.assign会直接返回该参数

如果该参数不是对象,则会先转成对象,然后返回

由于undefined和null无法转成对象,所以如果它们作为参数,就会报错


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

如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错

let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true

其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果

因为只有字符串的包装对象,会产生可枚举属性

const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

注意点


Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用

于嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加

Object.assign可以用来处理数组,但是会把数组视为对象

Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制


onst source = {
  get foo() { return 1 }
};
const target = {};
Object.assign(target, source)
// { foo: 1 }

常见用途


为对象添加属性

class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
    // 等价于 this.x = x; this.y = y
  }
}

为对象添加方法

Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

合并多个对象

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

为属性指定默认值

const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};
function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}
// 注意,由于存在浅拷贝的问题,DEFAULTS对象和options对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,DEFAULTS对象的该属性很可能不起作用。
const DEFAULTS = {
  url: {
    host: 'example.com',
    port: 7070
  },
};
processContent({ url: {port: 8000} })
// {
//   url: {port: 8000}
// }

Object.setPrototypeOf()


用来设置一个对象的prototype对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法

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

super 关键字


指向当前对象的原型对象

const proto = {
  foo: 'hello'
};
const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法


JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo(属性)或Object.getPrototypeOf(this).foo.call(this)(方法)


Object.keys


ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名

ES2017 引入了跟Object.keys配套的Object.values和Object.entries,作为遍历一个对象的补充手段,供for…of循环使用


let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };
for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}
for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}
for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}

symbol


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


// Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分
let s1 = Symbol('foo');
let s2 = Symbol();
s1 // Symbol(foo)
s2 // Symbol()
s1.toString() // "Symbol(foo)"
s2.toString() // ""

Symbol 值不能与其他类型的值进行运算,否则会报错

Symbol 值可以显式转为字符串

let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'

属性名的遍历


ymbol 作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名

另一个新的 API,Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。


let obj = {
  [Symbol('my_key')]: 1,
  enum: 2,
  nonEnum: 3
};
Reflect.ownKeys(obj)
//  ["enum", "nonEnum", Symbol(my_key)]

Symbol.for(),Symbol.keyFor()


有时,我们希望重新使用同一个 Symbol 值,Symbol.for方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值

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


let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
// Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
// s2未登记,所以返回undefined
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined

需要注意的是,Symbol.for为 Symbol 值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值


Set


ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set 本身是一个构造函数,用来生成 Set 数据结构


const s = new Set();
// add方法用来向Set里面添加数据
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
  console.log(i);
}
// 2 3 5 4

Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化

// 例一
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
// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56

在 Set 内部,两个NaN是相等

另外,两个对象总是不相等的

let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2

Set 实例的属性和方法


Set 结构的实例的属性


Set.prototype.constructor:构造函数,默认就是Set函数。

Set.prototype.size:返回Set实例的成员总数。


Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)


操作方法


add(value):添加某个值,返回 Set 结构本身。

delete(value):删除某个值,返回一个布尔值,表示删除是否成功。

has(value):返回一个布尔值,表示该值是否为Set的成员。

clear():清除所有成员,没有返回值。


// 去除数组重复成员的另一种方法
function dedupe(array) {
  return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]

遍历操作


keys():返回键名的遍历器

values():返回键值的遍历器

entries():返回键值对的遍历器

forEach():使用回调函数遍历每个成员

Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用

keys方法、values方法、entries方法返回的都是遍历器对象。由于 Set 结构没有键名,只有键值,所以keys方法和values方法的行为完全一致。


for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
//forEach() 对某个成员执行某种操作
set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

应用


扩展运算符(…)内部使用for…of循环,所以也可以用于 Set 结构

// 转换为数组
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']
//数组去重
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
// 实现交集,并集,差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

Map


ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。


//Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组
const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。

const set = new Set([
  ['foo', 1],
  ['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3

Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键


我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名


实例的属性和操作方法(1)size 属性


属性返回 Map 结构的成员总数

(2)set(key, value)

set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键

(3)get(key)

get方法读取key对应的键值,如果找不到key,返回undefined

(4)has(key)

has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中

(5)delete(key)

delete方法删除某个键,返回true。如果删除失败,返回false。

(6)clear()

clear方法清除所有成员,没有返回值


Map 结构转为数组结构,比较快速的方法是使用扩展运算符(…)


const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

结合数组的map方法、filter方法,可以实现 Map 的遍历和过滤

const map0 = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');
const map1 = new Map(
  [...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}
const map2 = new Map(
  [...map0].map(([k, v]) => [k * 2, '_' + v])
    );
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}

此外,Map 还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历

map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value);
});
// forEach方法还可以接受第二个参数,用来绑定this

WeakMap


WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名

WeakMap的键名所指向的对象,不计入垃圾回收机制

WeakMap的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用

WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用

总之,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。


应用


向dom节点存入数据


let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();
myWeakmap.set(myElement, {timesClicked: 0});
myElement.addEventListener('click', function() {
  let logoData = myWeakmap.get(myElement);
  logoData.timesClicked++;
}, false);
// 上面代码中,myElement是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是myElement。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

WeakMap 的另一个用处是部署私有属性

const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  dec() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}
const c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
// DONE

Promise


Promise是一种异步编程的解决方案。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部


const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署


resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。


// 图片异步加载
function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    const image = new Image();
    image.onload = function() {
      resolve(image);
    };
    image.onerror = function() {
      reject(new Error('Could not load image at ' + url));
    };
    image.src = url;
  });
}
// 封装ajax
const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();
  });
  return promise;
};
getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});

resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例

const p1 = new Promise(function (resolve, reject) {
  // ...
});
const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})

这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行


注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。


new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

Promise.prototype.then()


then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法

Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。


Promise.prototype.finally()


Promise.all()


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

Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。


// 生成一个Promise对象的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
  // ...
}).catch(function(reason){
  // ...
});
// 只有这 6 个实例的状态都变成fulfilled,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法


Promise.race()


Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例

只要有一个实例率先改变状态,Promise.race的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给Promise.race的回调函数。


// 下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve
const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);
p
.then(console.log)
.catch(console.error);

Promise.resolve()


将现有对象转为 Promise 对象

// (1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
//  参数是一个thenable对象
Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
// (3)参数不是具有then方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved
// (4)不带有任何参数
直接返回一个resolved状态的 Promise 对象。

Generator函数的异步应用


1.异步:所谓”异步”,简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。

var readFile = require('fs-readfile-promise');
readFile(fileA)
.then(function (data) {
  console.log(data.toString());
})
.then(function () {
  return readFile(fileB);
})
.then(function (data) {
  console.log(data.toString());
})
.catch(function (err) {
  console.log(err);
});

2.Thunkify 模块

// npm install thunkify
var thunkify = require('thunkify');
var fs = require('fs');
var read = thunkify(fs.readFile);
read('package.json')(function(err, str){
  // ...
});

async函数


async函数返回一个 Promise 对象

async函数内部return语句返回的值,会成为then方法回调函数的参数

async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到


async function f() {
  throw new Error('出错了');
}
f().then(
  v => console.log(v),
  e => console.log(e)
)
// Error: 出错了

Promise 对象的状态变化: async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。


await 命令: 正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象


// 只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行
async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}

下面的例子使用try…catch结构,实现多次重复尝试

const superagent = require('superagent');
const NUM_RETRIES = 3;
async function test() {
  let i;
  for (i = 0; i < NUM_RETRIES; ++i) {
    try {
      await superagent.get('http://google.com/this-throws-an-error');
      break;
    } catch(err) {}
  }
  console.log(i); // 3
}
test();
目录
相关文章
|
6月前
|
JavaScript 前端开发 开发者
ES6的常用新特性17条
ES6,也称为ECMAScript 2015,是JavaScript语言的一个重大更新,它引入了许多新的语法特性。以下是ES6的一些主要语法
|
6天前
|
前端开发
ES6 中常用的新特性
ES6 中常用的新特性
|
3月前
|
JavaScript 前端开发
ES6新特性(一)
ES6新特性(一)
|
6月前
|
JavaScript 前端开发 开发者
es6的新特性
es6的新特性
55 7
|
JavaScript 前端开发 网络架构
关于ES6新特性的总结 1
关于ES6新特性的总结
54 0
|
6月前
|
JavaScript
ES6 新特性 ES6使用 超实用
ES6 新特性 ES6使用 超实用
|
6月前
|
前端开发
ES6 部分新特性使用
ES6 部分新特性使用
36 0
|
JavaScript 前端开发 开发者
ES6 新特性
这是每个前端开发者都应该掌握的技能。ES6是JavaScript的一个重要的版本升级,它带来了许多新特性和语法糖,让我们的代码更加简洁高效。无论你是新手还是有经验的开发者,掌握ES6都会让你的编码变得更加愉快和高效。那么,让我们开始吧!
|
前端开发
关于ES6新特性的总结 2
关于ES6新特性的总结
41 0
es6的新特性使用
使用 命令有: export、import 、export default
110 0