新的数据结构Set相似于数组,可是成员的值都是惟一的,没有重复的值。Set 自己是一个构造函数,用来生成 Set 数据结构。接受一个数组(或相似数组的对象)做为参数,用来初始化。git
const set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4]
由于Set的成员都是惟一的,因此可用set去除数组重复成员
。向Set加入值的时候,不会发生类型转换,因此5和"5"是两个不一样的值。Set内部判断两个值是否不一样相似于精确相等运算符(===),主要的区别是NaN等于自身,而精确相等运算符认为NaN不等于自身。Array.from方法能够将 Set 结构转为数组。es6
// 去除数组的重复成员 [...new Set(array)] let set = new Set(); let a = NaN; let b = NaN; set.add(a); set.add(b); set // Set(1) {NaN} 这代表,在 Set 内部,两个NaN是相等。
操做方法github
遍历操做算法
因为 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),因此keys方法和values方法的行为彻底一致。express
Set的遍历顺序就是插入顺序。
经过该特性,当用Set保存一个回调函数列表,调用时就能保证按照添加顺序调用。编程
Set 结构的实例默承认遍历,它的默认遍历器生成函数就是它的values方法。扩展运算符(...)可用于 Set 结构。json
Set.prototype[Symbol.iterator] === Set.prototype.values // true
WeakSet 是一个构造函数,可使用new命令,建立 WeakSet 数据结构。能够接受一个数组或相似数组的对象做为参数。(实际上,任何具备 Iterable 接口的对象,均可以做为 WeakSet 的参数。)该数组的全部成员,都会自动成为 WeakSet 实例对象的成员。数组
const a = [[1, 2], [3, 4]]; const ws = new WeakSet(a); // WeakSet {[1, 2], [3, 4]}
上面的代码中,a数组的成员成为 WeakSet 的成员,而不是a数组自己。promise
WeakSet 结构有如下三个方法:数据结构
WeakSet 结构与 Set 相似,也是不重复的值的集合。可是,与Set不一样的是:
WeakSet的以上特性决定WeakSet适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakMap 里面的引用就会自动消失。
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),可是传统上只能用字符串看成键。
Map 数据结构相似于对象,也是键值对的集合,可是“键”的范围不限于字符串,各类类型的值(包括对象)均可以看成键。Map做为构造函数,能够接受任何具备 Iterator 接口的数据结构做为参数,好比数组。
//Map构造函数接受数组做为参数,实际上执行的是下面的算法。 const items = [ ['name', '张三'], ['title', 'Author'] ]; const map = new Map(); items.forEach( ([key, value]) => map.set(key, value) );
只有对同一个对象的引用,Map 结构才将其视为同一个键,由于Map 的键其实是跟内存地址绑定。
const map = new Map(); map.set(['a'], 555); map.get(['a']) // undefined
遍历方法
Map 的遍历顺序就是插入顺序。
Map 结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。Map 结构转为数组结构,比较快速的方法是使用扩展运算符(...)。
WeakMap结构与Map结构相似,也是用于生成键值对的集合。可是
WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。所以,只要所引用的对象的其余引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦再也不须要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
WeakMap结构有助于防止内存泄漏。
WeakMap没有遍历操做(即没有key()、values()和entries()方法),也没有size属性,也不支持clear方法。所以,WeakMap只有四个方法可用:get()、set()、has()、delete()。
Promise对象特色:
缺点:
Promise对象是一个构造函数,用来生成Promise实例。
var promise = new Promise( function(resolve, reject) { // ... some code if (/* 异步操做成功 */){ resolve(value); } else { reject(error); } });
Promise构造函数接受一个函数做为参数,该函数在Promise构造函数返回新建对象前被调用
,被传递resolve和reject函数。resolve和reject函数由JavaScript引擎提供,不用本身部署。若参数函数抛出一个错误,那么该promise 状态为rejected。函数的返回值被忽略。
resolve函数:将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),将传给resolve函数的参数传递出去。
reject函数:将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),将传给Promise函数的参数传递出去。简而言之,若是调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。
resolve函数能够传递一个Promise实例。当传递的是一个Promise实例时,其自身状态无效,其状态由该Promise实例决定。
var p1 = new Promise(function (resolve, reject) { // ... }); var p2 = new Promise(function (resolve, reject) { // ... resolve(p1); })
上面代码中p2的resolve方法将p1做为参数,即一个异步操做的结果是返回另外一个异步操做。
注意,这时p1的状态就会传递给p2,也就是说,这时p2本身的状态无效了,由p1的状态决定p2的状态若是p1的状态是Pending,那么p2的回调函数就会等待p1的状态改变;若是p1的状态已是Resolved或者Rejected,那么p2的回调函数将会马上执行。
the()方法返回一个新的Promise。所以能够采用链式写法。
promise.then(onFulfilled, onRejected); promise.then(function(value) { // success }, function(error) { // failure });
then方法能够接受两个回调函数做为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。这两个函数都接受Promise对象传出的值做为参数。若省略这两个参数,或者提供非函数,不会产生任何错误。
注意:
onFulfilled 或者 onRejected是被异步调用的。异步调用指的是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时执行。
getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // ... });
上面的代码中第一个回调函数完成之后,会将返回的json.post做为参数,传入第二个回调函数。若前一个回调函数返回的是一个Promise对象,这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
setTimeout(function(){ console.log("aaa"); }); // using a resolved promise, the 'then' block will be triggered instantly, but its handlers will be triggered asynchronously as demonstrated by the console.logs var resolvedProm = Promise.resolve(33); var thenProm = resolvedProm.then(function(value){ console.log("this gets called after the end of the main stack. the value received and returned is: " + value); return value; }); // instantly logging the value of thenProm console.log(thenProm); // using setTimeout we can postpone the execution of a function to the moment the stack is empty setTimeout(function(){ console.log(thenProm); }); //Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} //this gets called after the end of the main stack. the value received and returned is: 33 //aaa //Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}
上面代码中:setTimeout(fn, 0)在下一轮“事件循环”开始时执行,onFulfilled 在本轮“事件循环”结束时执行,console.log(thenProm)则是当即执行,所以最早输出。
若then中无对应的回调函数,则then返回的新promise将会保持原promise的状态进行调用。
例如:
function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms); }); } timeout(100).then(null, (value) => { console.log("aaa"); }).then((value) => { console.log("ccc"); }, (t) => { console.log("ddd"); }); //ccc
上面代码中,timeout函数中的 Promise状态是resolve,可是第一个then中没有对应的回调函数,所以第一个then返回的是resolve状态的Promise。因此第二个then立马被调用,输出"ccc"。
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。该方法返回一个新的Promise。
p.catch(onRejected); p.catch(function(reason) { // 拒绝 });
onRejected 抛出一个错误,或者返回一个拒绝的 Promise,则catch返回一个 rejected Promise,不然返回一个resolved Promise。
getJSON('/posts.json').then(function(posts) { // ... }).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log('发生错误!', error); });
上面代码中,getJSON方法返回一个 Promise 对象,若是该对象状态变为Resolved,则会调用then方法指定的回调函数;若是异步操做抛出错误,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,若是运行中抛出错误,也会被catch方法捕获。
通常来讲,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),老是使用catch方法。
// bad promise .then(function(data) { // success }, function(err) { // error }); // good promise .then(function(data) { //cb // success }) .catch(function(err) { // error });
由于第二种写法能够捕获前面then方法执行中的错误,因此建议老是使用catch方法,而不使用then方法的第二个参数。
重要解析:
var promise = new Promise(function(resolve, reject) { resolve('ok'); throw new Error('test'); }); promise.then(function(value) { console.log(value) }); //ok var promise = new Promise(function(resolve, reject) { resolve('ok'); setTimeout(function() { throw new Error('test') }, 0) }); promise.then(function(value) { console.log(value) }); // ok // Uncaught Error: test
上面代码中第一个例子中,throw 在resolve语句后面,抛出的错误,已经被捕获并处理。可是Promise 的状态由于resolve('ok')语句已改变,因此不会再改变。
上面代码中第二个例子中抛出错误时,Promise函数体已经运行结束,因此没法捕捉到该错误,就出现了在console中出现"ok"并抛出异常的现象。
详见Promise源码中的tryCallTwo和doResolve函数
Promise.all(iterable):当在可迭代参数中的全部promises被resolve,或者任一 Promise 被 reject时,返回一个新的promise。
iterable:一个可迭代对象,例如 Array。
(1)iterable为空(好比[]),返回一个同步的
resolved Promise。
(2)iterable未包含任何的promises(好比[1,2,3]),返回一个异步的
resolved Promise。
(3)iterable中的全部promises都是resolve,返回一个异步的
resolved Promise。
以上状况中,iterable内的全部值将组成一个数组,传递给回调函数。
var p1 = Promise.resolve(3); var p2 = 1337; var p3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); Promise.all([p1, p2, p3]).then(values => { console.log(values); // [3, 1337, "foo"] });
(4)只要iterable中的promises有一个被rejected,就当即返回一个异步的
rejected Promise。此时第一个被reject的实例的返回值,会传递给回调函数。
Promise.all([1,2,3, Promise.reject(555)]); //Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: 555}
如何理解返回一个异步的Promise
var p = Promise.all([]); // will be immediately resolved var p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously console.log(p); console.log(p2) setTimeout(function(){ console.log('the stack is now empty'); console.log(p2); }); // Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: Array(0)} // Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} // the stack is now empty // Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: Array(2)}
Promise.race(iterable):方法返回一个新的异步的
promise,参数iterable中只要有一个promise对象"完成(resolve)"或"失败(reject)",新的promise就会马上"完成(resolve)"或者"失败(reject)",并得到以前那个promise对象的返回值或者错误缘由。
var resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)]; var p = Promise.race(resolvedPromisesArray); // immediately logging the value of p console.log(p); // using setTimeout we can execute code after the stack is empty setTimeout(function(){ console.log('the stack is now empty'); console.log(p); }); //Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} //98 //the stack is now empty //Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}
若iterable为空,则返回的promise永远都是pending状态。
若iterable里面包含一个或多个非promise值而且/或者有一个resolved/rejected promise,则新生成的Promise的值为数组中的能被找到的第一个值。
var foreverPendingPromise = Promise.race([]); var alreadyResolvedProm = Promise.resolve(666); var arr = [foreverPendingPromise, alreadyResolvedProm, "non-Promise value"]; var arr2 = [foreverPendingPromise, "non-Promise value", Promise.resolve(666)]; var p = Promise.race(arr); var p2 = Promise.race(arr2); console.log(p); console.log(p2); setTimeout(function(){ console.log('the stack is now empty'); console.log(p); console.log(p2); }); //Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} //Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} //the stack is now empty //Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 666} //Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "non-Promise value"}
Promise.resolve返回一个Promise对象。
Promise.resolve(value); Promise.resolve(promise); Promise.resolve(thenable);
Promise.resolve方法的参数:
参数是一个thenable对象:thenable对象指的是具备then方法的对象。Promise.resolve方法将该对象转为Promise对象后,就会当即执行thenable对象的then方法。
let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // 42 }); //thenable对象的then方法执行后,对象p1的状态就变为resolved,从而当即执行最后那个then方法指定的回调函数,输出42。
其余状况:Promise.resolve方法返回一个新的Promise对象,状态为Resolved。Promise.resolve方法的参数,会同时传给回调函数。
var p = Promise.resolve('Hello'); p.then(function (s){ console.log(s) }); // Hello //返回Promise实例的状态从一辈子成就是Resolved,因此回调函数会当即执行
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。所以,回调函数会当即执行。
Promise.reject(reason);
Promise.reject()方法的参数,会原封不动地做为返回的新Promise的[[PromiseValue]]值,变成后续方法的参数。
JavaScript原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6又添加了Map和Set。一个数据结构只要部署了Symbol.iterator属性,就被视为具备iterator接口,就能够用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。任何数据结构只要部署了Iterator接口,就称这种数据结构是”可遍历的“(iterable)。
Symbol.iterator属性自己是一个函数,执行这个函数,就会返回一个遍历器。属性名Symbol.iterator是一个表达式,返回Symbol对象的iterator属性,这是一个预约义好的、类型为Symbol的特殊值,因此要放在方括号内。
遍历器对象的根本特征: 具备next方法。每次调用next方法,都会返回一个表明当前成员的信息对象,该对象具备value和done两个属性。
内置可迭代对象:String, Array, TypedArray, Map and Set
。
接受可迭代对象做为参数的:Map([iterable]), WeakMap([iterable]), Set([iterable])、WeakSet([iterable])、Promise.all(iterable), Promise.race(iterable) 以及 Array.from()。
一个对象若是要有可被for...of循环调用的Iterator接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具备该方法也可)。
对于相似数组的对象(存在数值键名和length属性),部署Iterator接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的Iterator接口。
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; // 或者 NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
若是Symbol.iterator方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。
扩展运算符:扩展运算符(...)也会调用默认的iterator接口。所以,可经过(...)方便的将部署了Iterator接口的数据接口转为数组。
let arr = [...iterable];
任何接受数组做为参数的场合,都调用了遍历器接口。
for...of Array.from() Map(), Set(), WeakMap(), WeakSet()(好比new Map([['a',1],['b',2]])) Promise.all() Promise.race()
for...in循环读取键名。for...of循环读取键值,但数组的遍历器接口只返回具备数字索引的键值。
let arr = [3, 5, 7]; arr.foo = 'hello'; for (let i in arr) { console.log(i); // "0", "1", "2", "foo" } for (let i of arr) { console.log(i); // "3", "5", "7" } //for...of循环不返回数组arr的foo属性
Set 和 Map 结构使用for...of循环时:
ES6的数组、Set、Map均有如下方法(返回的都是遍历器对象,与Object的entries、keys、values方法不一样,Object返回的均是数组。):
for...of循环能正确识别字符串中的32位 UTF-16 字符。
可经过Array.from方法将相似数组的对象转为数组。
for...of循环能够与break、continue和return配合使用,提供了遍历全部数据结构的统一操做接口。
Generator 函数是一个普通函数,有如下特征:
调用Generator 函数,就是在函数名后面加上一对圆括号。不过,调用 Generator 函数后,该函数并不执行,而是返回一个遍历器对象。调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性就是yield表达式或return后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。Generator 函数不能当构造器使用。
function* f() {} var obj = new f; // throws "TypeError: f is not a constructor"
遍历器对象的next方法的运行逻辑:
function* demo() { console.log('Hello' + (yield)); console.log('Hello' + (yield 123)); } var a=demo(); a.next(); //Object {value: undefined, done: false} 第一次运行了yield以后就中止了。 a.next(); //Helloundefined //Object {value: 123, done: false} 第二次将以前的hello打印,并运行yield 123以后中止。 a.next(); //Helloundefined //Object {value: undefined, done: true}
yield表达式与return语句:
类似之处:能返回紧跟在语句后面的那个表达式的值。
不一样之处:每次遇到yield,函数暂停执行,下一次再从该位置后继续向后执行,即便运行到最后一个yield ,其返回对象的done仍为false。return语句执行后即表明该遍历结束,返回对象的done为true。
function* helloWorldGenerator() { yield 'hello'; return 'ending'; yield 'world'; } var hw = helloWorldGenerator(); hw.next(); // Object {value: "hello", done: false} hw.next(); // Object {value: "ending", done: true} hw.next(); // Object {value: undefined, done: true}
Generator 函数能够不用yield表达式,这时就变成了一个单纯的暂缓执行函数。但yield表达式只能用在 Generator 函数里面,用在其余地方都会报错。
function* f() { console.log('执行了!') } var generator = f(); setTimeout(function () { generator.next() }, 2000);
上面代码中函数f若是是普通函数,在为变量generator赋值时就会执行。可是,函数f是一个 Generator 函数,就变成只有调用next方法时,函数f才会执行。
yield表达式若是用在另外一个表达式之中,必须放在圆括号里面。若是用做函数参数或放在赋值表达式的右边,能够不加括号。
任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。因为 Generator 函数就是遍历器生成函数,所以能够把 Generator 赋值给对象的Symbol.iterator属性。
Generator 函数执行后,返回一个遍历器对象。该对象自己也具备Symbol.iterator属性,执行后
返回自身。
function* gen(){ // some code } var g = gen(); g[Symbol.iterator]() === g // true
yield表达式自己没有返回值,或者说老是返回undefined。next方法能够带一个参数,该参数就会被看成上一个yield表达式的返回值。注意,因为next方法的参数表示上一个yield表达式的返回值,因此第一次使用next方法时,不用带参数。
for...of循环能够自动遍历 Generator 函数时生成的Iterator对象。
function *foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 1 2 3 4 5
注意:一旦next方法的返回对象的done属性为true,for...of循环就会停止,且不包含该返回对象,因此上面代码的return语句返回的6,不包括在for...of循环之中。
for...of的本质是一个while循环,通俗的讲,就是运行对象的Symbol.iterator方法(即遍历器生成函数),获得遍历器对象,再不停的调用遍历器对象的next方法运行,直到遍历结束。
相似以下:
var it = foo(); var res = it.next(); while (!res.done){ // ... res = it.next(); }
throw() 方法:向Generator函数内部抛出异常,并恢复生成器的执行,返回带有 done 及 value 两个属性的对象。
gen.throw(exception)
exception:要抛出的异常。
该方法能够在Generator 函数体外抛出错误,而后在 Generator 函数体内捕获。
var g = function* () { try { yield; } catch (e) { console.log('内部捕获', e); } }; var i = g(); i.next(); try { i.throw('a'); i.throw('b'); } catch (e) { console.log('外部捕获', e); } // 内部捕获 a // 外部捕获 b
上面代码中,遍历器对象i连续抛出两个错误。第一个错误被 Generator 函数体内的catch语句捕获。i第二次抛出错误,因为 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,因此这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。
throw方法能够接受一个参数,该参数会被catch语句接收,建议抛出Error对象的实例。遍历器对象的throw方法和全局的throw命令不同。全局的throw命令只能被该命令外的catch语句捕获,且不会再继续try代码块里面剩余的语句了。
若是 Generator 函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获。若是 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行。
var g = function* () { while (true) { yield; console.log('内部捕获', e); } }; var i = g(); i.next(); try { i.throw('a'); i.throw('b'); } catch (e) { console.log('外部捕获', e); } // 外部捕获 a
throw方法被捕获之后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。
var gen = function* gen(){ try { yield console.log('a'); console.log('b'); } catch (e) { console.log('错误被捕获'); } yield console.log('c'); yield console.log('d'); } var g = gen(); g.next(); //a //Object {value: undefined, done: false} g.throw(); //错误被捕获 //c //Object {value: undefined, done: false} g.next(); //d //Object {value: undefined, done: false}
上面的代码能够看出,g.throw方法是先抛出异常,再自动执行一次next方法,所以能够看到没有打印b,可是打印了c。
Generator 函数体外抛出的错误,能够在函数体内捕获;反过来,Generator 函数体内抛出的错误,也能够被函数体外的catch捕获。
一旦 Generator 执行过程当中抛出错误,且没有被内部捕获,就不会再执行下去了。若是此后还调用next方法,将返回一个value属性等于undefined、done属性等于true的对象,即 JavaScript 引擎认为这个 Generator 已经运行结束了。
Generator.prototype.return能够返回给定的值,而且终结遍历Generator函数。若该方法被调用时,Generator函数已结束,则Generator函数将保持结束的状态,可是提供的参数将被设置为返回对象的value属性的值。
遍历器对象调用return方法后,返回值的value属性就是return方法的参数foo。而且,Generator函数的遍历就终止了,返回值的done属性为true,之后再调用next方法,done属性老是返回true。若是return方法调用时,不提供参数,则返回值的value属性为undefined。
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 } g.return() // {value: undefined, done: true} g.return('111') //{value: "111", done: true}
上面代码中,g.return('111')调用时, Generator函数的遍历已经终止,因此返回的对象的done值仍为true,可是value值会被设置为'111'。
若是 Generator 函数内部有try...finally代码块,那么当return方法执行时的语句在 Generator 函数内部的try代码块中时,return方法会推迟到finally代码块执行完再执行。
function* numbers () { yield 1; try { yield 2; yield 3; } finally { yield 4; yield 5; } yield 6; } var g = numbers(); g.next(); //Object {value: 1, done: false} g.return(7); //Object {value: 7, done: true} //return执行时还未在try语句块内,因此返回{value: 7, done: true}并终止遍历。 function* numbers () { yield 1; try { yield 2; yield 3; yield 33; } finally { yield 4; yield 5; } yield 6; } var g = numbers(); g.next(); // Object {value: 1, done: false} g.next(); // Object {value: 2, done: false} g.return(7); // Object {value: 4, done: false} g.next(); // Object {value: 5, done: false} g.next(); // Object {value: 7, done: true} //return执行时已在try语句块内,运行时直接跳至finally语句块执行,并在该语句块内的代码执行完后,因此返回{value: 7, done: true}并终止遍历。
yield* expression :expression 能够是一个generator 或可迭代对象。yield * 表达式自身的值是当迭代器关闭时返回的值(即,当done时为true)。
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"
yield*后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for...of循环。有return语句时,若想遍历出return返回的值,则须要用var value = yield* iterator的形式获取return语句的值。
function* foo() { yield 'a'; yield 'b'; return "mm"; } function* bar() { yield 'x'; yield* foo(); yield 'y'; } for(var t of bar()){ console.log(t); } //x //a //b //y //yield* foo()写法没法遍历出foo里面的“mm”。 function* foo() { yield 'a'; yield 'b'; return "mm"; } function* bar() { yield 'x'; yield yield* foo(); yield 'y'; } for(var t of bar()){ console.log(t); } //x //a //b //mm //y //yield* foo()运行返回的值就是“mm”,因此yield yield* foo()能够遍历出“mm”。
任何数据结构只要有 Iterator 接口,就能够被yield*遍历。
yield*就至关因而使用for...of进行了循环。
若是一个对象的属性是 Generator 函数,则需在属性前面加一个星号。
let obj = { * myGeneratorMethod() { ··· } }; //等同于 let obj = { myGeneratorMethod: function* () { // ··· } };
Generator 函数老是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法
。Generator函数不能跟new命令一块儿用,会报错。
function* g() {} g.prototype.hello = function () { return 'hi!'; }; let obj = g(); obj.hello() // 'hi!'
上面代码能够看出,obj对象是Generator 函数g的实例。可是,若是把g看成普通的构造函数,并不会生效,由于g返回的老是遍历器对象,而不是this对象。
function* g() { this.a = 11; } let obj = g(); obj.a // undefined
上面代码中,Generator函数g在this对象上面添加了一个属性a,可是obj对象拿不到这个属性。
用来处理异步操做,改写回调函数。即把异步操做写在yield表达式里面,异步操做的后续操做放在yield表达式下面。
function* main() { var result = yield request("http://some.url"); var resp = JSON.parse(result); console.log(resp.value); } function request(url) { makeAjaxCall(url, function(response){ it.next(response); }); } var it = main(); it.next(); //上面为经过 Generator 函数部署 Ajax 操做。
Generator 函数能够暂停执行和恢复执行,这是它能封装异步任务的根本缘由。整个 Generator 函数就是一个封装的异步任务。异步操做须要暂停的地方,都用yield语句注明。
Generator 函数能够进行数据交换。next返回值的value属性,是 Generator 函数向外输出数据;next方法还能够接受参数,向 Generator 函数体内输入数据。
Generator 函数能够部署错误处理代码,捕获函数体外抛出的错误。
function* gen(x){ try { var y = yield x + 2; } catch (e){ console.log(e); } return y; } var g = gen(1); g.next(); g.throw('出错了'); // 出错了
上面代码的最后一行,Generator 函数体外,使用指针对象的throw方法抛出的错误,能够被函数体内的try...catch代码块捕获。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。
async函数返回一个 Promise 对象,可使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操做完成,再接着执行函数体内后面的语句
。
function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } async function add1(x) { var a = resolveAfter2Seconds(20); var b = resolveAfter2Seconds(30); return x + await a + await b; } add1(10).then(v => { console.log(v); // prints 60 after 2 seconds. }); async function add2(x) { var a = await resolveAfter2Seconds(20); var b = await resolveAfter2Seconds(30); return x + a + b; } add2(10).then(v => { console.log(v); // prints 60 after 4 seconds. });
async 函数有多种使用形式。
// 函数声明 async function foo() {} // 函数表达式 const foo = async function () {}; // 对象的方法 let obj = { async foo() {} }; obj.foo().then(...) // Class 的方法 class Storage { constructor() { this.cachePromise = caches.open('avatars'); } async getAvatar(name) { const cache = await this.cachePromise; return cache.match(`/avatars/${name}.jpg`); } } const storage = new Storage(); storage.getAvatar('jake').then(…); // 箭头函数 const foo = async () => {};
调用async函数时会返回一个 promise 对象。当这个async函数返回一个值时,promise 的 resolve 方法将会处理这个返回值;当异步函数抛出的是异常或者非法值时,promise 的 reject 方法将处理这个异常值。
async function f() { throw new Error('出错了'); } f().then( v => console.log(v), e => console.log(e) ) // Error: 出错了
async函数返回的 Promise 对象,必须等到内部全部await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操做执行完,才会执行then方法指定的回调函数
。
await expression:会形成异步函数中止执行而且等待 promise 的解决后再恢复执行。若expression是Promise 对象,则返回expression的[[PromiseValue]]值,若expression不是Promise 对象,则直接返回该expression。
async function f2() { var y = await 20; console.log(y); // 20 } f2();
await命令后面通常是一个 Promise 对象。若是不是,会被转成一个当即resolve的 Promise 对象。await命令后面的 Promise 对象若是变为reject状态,则会throws异常值,所以reject的参数会被catch方法的回调函数接收到。只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
async function f() { await Promise.reject('出错了'); await Promise.resolve('hello world'); // 第二个await语句是不会执行的 }
若是await后面的异步操做出错,那么等同于async函数返回的 Promise 对象被reject。防止出错的方法,是将其放在try...catch代码块之中。
async function f() { await new Promise(function (resolve, reject) { throw new Error('出错了'); }); } f() .then(v => console.log(v)) .catch(e => console.log(e)) // Error:出错了
上面代码中,async函数f执行后,await后面的 Promise 对象会抛出一个错误对象,致使catch方法的回调函数被调用,它的参数就是抛出的错误对象。
最好把await命令放在try...catch代码块中。由于await命令后面的Promise对象,运行结果多是rejected。
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另外一种写法 async function myFunction() { await somethingThatReturnsAPromise() .catch(function (err) { console.log(err); }; }
多个await命令后面的异步操做,若是不存在继发关系,最好让它们同时触发。同时触发可使用Promise.all。
async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); }
await命令只能用在async函数之中,若是用在普通函数,就会报错。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 报错。 由于await用在普通函数之中 docs.forEach(function (doc) { await db.post(doc); }); }
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
异步遍历器的最大的语法特色,就是调用遍历器的next方法,返回的是一个 Promise 对象。
asyncIterator .next() .then( ({ value, done }) => /* ... */ );
上面代码中,asyncIterator是一个异步遍历器,调用next方法之后,返回一个 Promise 对象。所以,可使用then方法指定,这个 Promise 对象的状态变为resolve之后的回调函数。回调函数的参数,则是一个具备value和done两个属性的对象,这个跟同步遍历器是同样的。
一个对象的同步遍历器的接口,部署在Symbol.iterator属性上面。一样地,对象的异步遍历器接口,部署在Symbol.asyncIterator属性上面。无论是什么样的对象,只要它的Symbol.asyncIterator属性有值,就表示应该对它进行异步遍历。
for...of循环用于遍历同步的 Iterator 接口。新引入的for await...of循环,则是用于遍历异步的 Iterator 接口。for await...of循环也能够用于同步遍历器。
async function f() { for await (const x of createAsyncIterable(['a', 'b'])) { console.log(x); } } // a // b
上面代码中,createAsyncIterable()返回一个异步遍历器,for...of循环自动调用这个遍历器的next方法,会获得一个Promise对象。await用来处理这个Promise对象,一旦resolve,就把获得的值(x)传入for...of的循环体。
在语法上,异步 Generator 函数就是async函数与 Generator 函数的结合。
async function* readLines(path) { let file = await fileOpen(path); try { while (!file.EOF) { yield await file.readLine(); } } finally { await file.close(); } }
上面代码中,异步操做前面使用await关键字标明,即await后面的操做,应该返回Promise对象。凡是使用yield关键字的地方,就是next方法的停下来的地方,它后面的表达式的值(即await file.readLine()的值),会做为next()返回对象的value属性。
constructor定义构造方法,this关键字表明实例对象。定义“类”的方法的时候,前面不须要加上function这个关键字,直接把函数定义放进去了就能够了。另外,方法之间不须要逗号分隔,加了会报错。类的数据类型就是函数,类的原型的constructor指向类自身
。使用的时候,对类使用new命令。
//定义类 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } static distance() { } } typeof Point // "function" Point === Point.prototype.constructor // true
类的通常的方法都定义在类的prototype属性上面。
在类的实例上面调用方法,其实就是调用原型上的方法。类的内部全部定义的方法,都是不可枚举的。类的静态方法只能用类来调用,不能用类的实例调用。若是在实例上调用静态方法,会抛出一个错误,表示不存在该方法。父类的静态方法,能够被子类继承。
class Point { constructor(){ // ... } toString(){ // ... } toValue(){ // ... } } // 等同于 Point.prototype = { toString(){}, toValue(){} };
类的属性名,能够采用表达式。
let methodName = "getArea"; class Square{ constructor(length) { // ... } [methodName]() { // ... } } //Square类的方法名getArea,是从表达式获得的。
constructor方法是类的默认方法,经过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,若是没有显式定义,一个空的constructor方法会被默认添加。
constructor方法默认返回实例对象(即this),也能够指定返回另一个对象。类的构造函数,不使用new是无法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者不用new也能够执行。
class Foo { constructor() { return Object.create(null); } } new Foo() instanceof Foo // false
上面代码中,constructor函数返回一个全新的对象,结果致使实例对象不是Foo类的实例。
生成类的实例对象的写法,也是使用new命令。若是忘记加上new,像函数那样调用Class,将会报错。类里面定义的属性除了定义在this上的,其余都是定义在原型上的。定义在this上的属性各实例对象各自有一份。类的全部实例共享一个原型对象。
Class不存在变量提高(hoist),所以先使用,后定义会报错。
new Foo(); // ReferenceError class Foo {}
类也可使用表达式的形式定义。
const MyClass = class Me { getClassName() { return Me.name; } };
上面代码使用表达式定义了一个类。须要注意的是,这个类的名字是MyClass而不是Me,Me只在Class的内部代码可用,指代当前类。
若是类的内部没用到的话,能够省略Me,也就是能够写成下面的形式。
const MyClass = class { /* ... */ };
采用Class表达式,能够写出当即执行的Class。
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }('张三'); person.sayName(); // "张三" // person是一个当即执行的类的实例。
ES6不提供私有方法。
类的方法内部若是含有this,它默认指向类的实例
。可是,必须很是当心,一旦单独使用该方法,极可能报错。注意,若是静态方法包含this关键字,这个this指的是类,而不是实例。
class Logger { printName(name = 'there') { this.print(`Hello ${name}`); } print(text) { console.log(text); } } const logger = new Logger(); const { printName } = logger; printName(); // TypeError: Cannot read property 'print' of undefined
上面代码中,printName方法中的this,默认指向Logger类的实例。可是,若是将这个方法提取出来单独使用,this会指向该方法运行时所在的环境,由于找不到print方法而致使报错。
一个比较简单的解决方法是,在构造方法中绑定this。
class Logger { constructor() { this.printName = this.printName.bind(this); } // ... }
另外一种解决方法是使用箭头函数。
还有一种解决方法是使用Proxy,获取方法的时候,自动绑定this。
function selfish (target) { const cache = new WeakMap(); const handler = { get (target, key) { const value = Reflect.get(target, key); if (typeof value !== 'function') { return value; } if (!cache.has(value)) { cache.set(value, value.bind(target)); } return cache.get(value); } }; const proxy = new Proxy(target, handler); return proxy; } const logger = selfish(new Logger());
类和模块的内部,默认就是严格模式
,因此不须要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
本质上,ES6的类只是ES5的构造函数的一层包装,因此函数的许多特性都被Class继承,包括name属性。name属性老是返回紧跟在class关键字后面的类名。
class Point {} Point.name // "Point"
Class之间能够经过extends关键字实现继承。
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 调用父类的toString() } }
子类必须在constructor方法中调用super方法,不然新建实例时会报错。这是由于子类没有本身的this对象,而是继承父类的this对象,而后对其进行加工。若是不调用super方法,子类就得不到this对象。
ES6的继承实质是先创造父类的实例对象this(因此必须先调用super方法),而后再用子类的构造函数修改this。
在子类的构造函数中,只有调用super以后,才可使用this关键字,不然会报错。这是由于子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。
若是子类没有定义constructor方法,如下方法会被默认添加。所以,无论有没有显式定义,任何一个子类都有constructor方法。
constructor(...args) { super(...args); }
Class同时有prototype属性和__proto__属性,所以同时存在两条继承链。
类的继承是按照下面的模式实现的。
class A { } class B { } Object.setPrototypeOf(B.prototype, A.prototype); Object.setPrototypeOf(B, A); const b = new B();
而Object.setPrototypeOf方法的实现以下:
Object.setPrototypeOf = function (obj, proto) { obj.__proto__ = proto; return obj; }
所以,就获得以下结果。
Object.setPrototypeOf(B.prototype, A.prototype); // 等同于 B.prototype.__proto__ = A.prototype; Object.setPrototypeOf(B, A); // 等同于 B.__proto__ = A;
extends关键字后面能够跟多种类型的值。
class B extends A { }
上面代码的A,只要是一个有prototype属性的函数,就能被B继承。因为函数都有prototype属性(除了Function.prototype函数),所以A能够是任意函数。
class A { } A.__proto__ === Function.prototype // true A.prototype.__proto__ === Object.prototype // true
上面代码中,A做为一个基类(即不存在任何继承),就是一个普通函数,因此直接继承Function.prototype。A.prototype是一个对象,因此A.prototype.__proto__指向构造函数(Object)的prototype属性。
class A extends null { } A.__proto__ === Function.prototype // true A.prototype.__proto__ === undefined // true //等同于 class C extends null { constructor() { return Object.create(null); } }
上面代码中,子类继承null。
Object.getPrototypeOf方法能够用来从子类上获取父类。所以,可使用这个方法判断,一个类是否继承了另外一个类。
Object.getPrototypeOf(ColorPoint) === Point //true
super这个关键字,既能够看成函数使用,也能够看成对象使用。
(一) super做为函数调用时,表明父类的构造函数,且super()只能用在子类的构造函数之中,用在其余地方就会报错。ES6 要求,子类的构造函数必须执行一次super函数。
class A {} class B extends A { constructor() { super(); } }
子类B的构造函数之中的super(),表明调用父类的构造函数。super()在这里至关于A.prototype.constructor.call(this)
。
(二) super做为对象时,在普通方法中,指向父类的原型对象(当指向父类的原型对象时,定义在父类实例上的方法或属性,是没法经过super调用的。);在静态方法中,指向父类。
class A { p() { return 2; } static m() { console.log("父类的m方法被调用") } } class B extends A { constructor() { super(); console.log(super.p()); // 2 } static show() { super.m(); } } let b = new B(); B.show(); //父类的m方法被调用
上面代码中,子类B的constructor中的super.p()在普通方法中,指向A.prototype,因此super.p()就至关于A.prototype.p()。子类B的show方法中的super.m()在静态方法中,因此super.m()就至关于A.m()。
ES6 规定,经过super调用父类的方法时,super会绑定子类的this。
class A { constructor() { this.x = 1; } print() { console.log(this.x); } } class B extends A { constructor() { super(); this.x = 2; } m() { super.print(); } } let b = new B(); b.m() // 2
上面代码中,super.print()虽然调用的是A.prototype.print(),可是A.prototype.print()会绑定子类B的this,致使输出的是2。也就是说,实际上执行的是super.print.call(this)。
经过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
class A { constructor() { this.x = 1; } } class B extends A { constructor() { super(); this.x = 2; super.x = 3; console.log(super.x); // undefined console.log(this.x); // 3 } } let b = new B();
上面代码中,super.x赋值为3,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,因此返回undefined。
注意,使用super的时候,必须显式指定是做为函数、仍是做为对象使用,不然会报错。
class A {} class B extends A { constructor() { super(); console.log(super); // 报错 } } //console.log(super)当中的super,没法看出是做为函数使用,仍是做为对象使用,因此 JavaScript 引擎解析代码的时候就会报错。
子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。
class Point{ } class ColorPoint extends Point{ constructor(){ super(); } } var p1 = new Point(); var p2 = new ColorPoint(); p2.__proto__.__proto__ === p1.__proto__ // true
原生构造函数是指语言内置的构造函数,一般用来生成数据结构。ECMAScript的原生构造函数大体有下面这些。
extends关键字不只能够用来继承类,还能够用来继承原生的构造函数。
注意,继承Object的子类,有一个行为差别。
class NewObj extends Object{ constructor(){ super(...arguments); } } var o = new NewObj({attr: true}); console.log(o.attr === true); // false
上面代码中,NewObj继承了Object,可是没法经过super方法向父类Object传参。这是由于ES6改变了Object构造函数的行为,一旦发现Object方法不是经过new Object()这种形式调用,ES6规定Object构造函数会忽略参数。
在Class内部可使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。存值函数和取值函数是设置在属性的descriptor对象上的。
class MyClass { constructor() { // ... } get prop() { return 'getter'; } set prop(value) { console.log('setter: '+value); } } let inst = new MyClass(); inst.prop = 123; // setter: 123 inst.prop // 'getter' //代码中,prop属性有对应的存值函数和取值函数,所以赋值和读取行为都被自定义了。
在一个方法前,加上static关键字,则是静态方法。静态方法不会被实例继承,而是直接经过类来调用。所以在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
class Foo { static classMethod() { return 'hello'; } } Foo.classMethod() // 'hello' var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function
注意,若是静态方法包含this关键字,这个this指的是类,而不是实例。
静态方法能够与非静态方法重名。父类的静态方法,能够被子类继承。
class Foo { static bar () { this.baz(); } static baz () { console.log('hello'); } baz () { console.log('world'); } } Foo.bar() // hello
上面代码中,静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。
静态属性指的是Class自己的属性,即Class.propname,而不是定义在实例对象(this)上的属性。由于ES6明确规定,Class内部只有静态方法,没有静态属性。因此目前只有下面这种写法。
//为Foo类定义了一个静态属性prop class Foo { } Foo.prop = 1; Foo.prop // 1
ES7有一个静态属性的提案,目前Babel转码器支持。这个提案规定:
类的实例属性能够用等式,写入类的定义之中。
class MyClass { myProp = 42; constructor() { console.log(this.myProp); // 42 } }
类的静态属性只要在上面的实例属性写法前面,加上static关键字就能够了。
// 老写法 class Foo { // ... } Foo.prop = 1; // 新写法 class Foo { static prop = 1; }
目前,有一个提案,为class加了私有属性。方法是在属性名以前,使用#表示。#也能够用来写私有方法。私有属性能够指定初始值,在构造函数执行时进行初始化。
class Point { #x; constructor(x = 0) { #x = +x; } get x() { return #x } set x(value) { #x = +value } #sum() { return #x; } }
上面代码中,#x就表示私有属性x,在Point类以外是读取不到这个属性的。还能够看到,私有属性与实例的属性是能够同名的(好比,#x与get x())。
ES6为new命令引入了一个new.target属性,(在构造函数中)返回new命令做用于的那个构造函数。若是构造函数不是经过new命令调用的,new.target会返回undefined,所以这个属性能够用来肯定构造函数是怎么调用的。
function Person(name) { if (new.target !== undefined) { this.name = name; } else { throw new Error('必须使用new生成实例'); } } // 另外一种写法 function Person(name) { if (new.target === Person) { this.name = name; } else { throw new Error('必须使用new生成实例'); } } var person = new Person('张三'); // 正确 var notAPerson = Person.call(person, '张三'); // 报错 //上面代码确保构造函数只能经过new命令调用。
Class内部调用new.target,返回当前Class。子类继承父类时,new.target会返回子类。在函数外部使用new.target会报错。
class Rectangle { constructor(length, width) { console.log(new.target === Rectangle); // ... } } class Square extends Rectangle { constructor(length) { super(length, length); } } var obj = new Square(3); // 输出 false
参考自:ECMAScript 6 入门