ES6进修笔记3-Set和Map、Promise、Iterator、Generator、async 、Class

Set和Map数据构造

Set

新的数据构造Set相似于数组,然则成员的值都是唯一的,没有反复的值。Set 本身是一个组织函数,用来天生 Set 数据构造。吸收一个数组(或相似数组的对象)作为参数,用来初始化。

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 构造转为数组。

// 去除数组的反复成员
[...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是相称。

Set 实例的属性和要领

Set 实例的属性

  1. size:返回Set实例的成员总数。
  2. constructor:指向组织函数,默许就是Set函数。

Set 实例的要领

操纵要领

  • add(value):增加某个值,返回Set构造本身。
  • delete(value):删除某个值,返回一个布尔值,示意删除是不是胜利。
  • has(value):返回一个布尔值,示意该值是不是为Set的成员。
  • clear():消灭一切成员,没有返回值。

遍历操纵

  • keys():返回键名的遍历器对象。
  • values():返回键值的遍历器对象。
  • entries():返回键值对的遍历器对象。
  • forEach():运用回调函数遍历每一个成员,没有返回值。第一个参数是一个处置惩罚函数。该函数的参数依次为键值、键名、鸠合本身。第二个参数,示意绑定的this对象。

因为 Set 构造没有键名,只需键值(或许说键名和键值是同一个值),所以keys要领和values要领的行动完整一致。

Set的遍历递次就是插进去递次。经由过程该特性,当用Set保留一个回调函数列表,挪用时就可以保证根据增加递次挪用。

Set 构造的实例默许可遍历,它的默许遍历器天生函数就是它的values要领。扩大运算符(…)可用于 Set 构造。

Set.prototype[Symbol.iterator] === Set.prototype.values
// true

WeakSet

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数组本身。

WeakSet 构造有以下三个要领:

  • WeakSet.prototype.add(value):向 WeakSet 实例增加一个新成员。
  • WeakSet.prototype.delete(value):消灭 WeakSet 实例的指定成员。
  • WeakSet.prototype.has(value):返回一个布尔值,示意某个值是不是在 WeakSet 实例当中。

WeakSet的特性

WeakSet 构造与 Set 相似,也是不反复的值的鸠合。然则,与Set差别的是:

  1. WeakSet 的成员只能是对象,而不能是其他范例的值。
  2. WeakSet 中的对象都是弱援用,即渣滓接纳机制不斟酌 WeakSet 对该对象的援用,也就是说,假如其他对象都不再援用该对象,那末渣滓接纳机制会自动接纳该对象所占用的内存,不斟酌该对象还存在于 WeakSet 当中。
  3. WeakSet 没有size属性,不可遍历。

WeakSet的以上特性决议WeakSet合适暂时寄存一组对象,以及寄存跟对象绑定的信息。只需这些对象在外部消逝,它在 WeakMap 内里的援用就会自动消逝。

Map

JavaScript 的对象(Object),本质上是键值对的鸠合(Hash 构造),然则传统上只能用字符串看成键。
Map 数据构造相似于对象,也是键值对的鸠合,然则“键”的局限不限于字符串,各种范例的值(包括对象)都能够看成键。Map作为组织函数,能够吸收任何具有 Iterator 接口的数据构造作为参数,比方数组。

//Map组织函数吸收数组作为参数,实际上实行的是下面的算法。
const items = [
  ['name', '张三'],
  ['title', 'Author']
];

const map = new Map();

items.forEach(
  ([key, value]) => map.set(key, value)
);
  1. 假如对同一个键屡次赋值,背面的值将掩盖前面的值。
  2. 假如读取一个未知的键,则返回undefined。
  3. 只需对同一个对象的援用,Map 构造才将其视为同一个键,因为Map 的键实际上是跟内存地址绑定。

    const map = new Map();
    map.set(['a'], 555);
    map.get(['a']) // undefined

推断Map键是不是相称

  1. Map键是对象范例的,内存地址雷同才雷同。
  2. Map键是简朴范例(数字、字符串、布尔值)的,两个值严厉相称视为一个键。0和-0是同一个键。
  3. Map键将NaN和其本身视为同一个键。

Map实例的属性和要领

Map实例的属性

  1. size:返回 Map 构造的成员总数。

Map实例的要领

  1. set(key, value):设置键名key对应的键值为value,然后返回全部 Map 构造。假如key已有值,则键值会被更新,不然就新天生该键。
  2. get(key):读取key对应的键值,假如找不到key,返回undefined。
  3. has(key):返回一个布尔值,示意某个键是不是在当前 Map 对象当中。
  4. delete(key):删除某个键,返回true。假如删除失利,返回false。
  5. clear():消灭一切成员,没有返回值。

遍历要领

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回一切成员的遍历器。
  • forEach():运用回调函数遍历Map的每一个成员。第一个参数是一个处置惩罚函数。该函数的参数依次为键值、键名、鸠合本身。第二个参数,示意绑定的this对象。

Map 的遍历递次就是插进去递次。

Map 构造的默许遍历器接口(Symbol.iterator属性),就是entries要领。Map 构造转为数组构造,比较疾速的要领是运用扩大运算符(…)。

WeakMap

WeakMap构造与Map构造相似,也是用于天生键值对的鸠合。然则

  1. WeakMap只吸收对象作为键名(null除外),不吸收其他范例的值作为键名。
  2. WeakMap的键名所指向的对象,不计入渣滓接纳机制。

WeakMap的键名所援用的对象都是弱援用,即渣滓接纳机制不将该援用斟酌在内。因而,只需所援用的对象的其他援用都被消灭,渣滓接纳机制就会开释该对象所占用的内存。也就是说,一旦不再须要,WeakMap 内里的键名对象和所对应的键值对会自动消逝,不必手动删除援用。

WeakMap构造有助于防备内存走漏。

WeakMap没有遍历操纵(即没有key()、values()和entries()要领),也没有size属性,也不支撑clear要领。因而,WeakMap只需四个要领可用:get()、set()、has()、delete()。

Promise

Promise对象特性:

  1. Promise对象有三种状况:Pending(举行中)、Resolved(已完成,又称 Fulfilled)和Rejected(已失利)。
  2. 一旦状况转变,就不会再变,任何时刻都能够取得这个效果。Promise对象的状况转变,只需两种能够:从Pending变成Resolved和从Pending变成Rejected。只需这两种状况发作,状况就不会再变了。假如转变已发作了,你再对Promise对象增加回调函数,也会马上取得这个效果。这与事宜(Event)完整差别,事宜的特性是,假如你错过了它,再去监听,是得不到效果的。
  3. 状况一转变,即挪用Promise 对象的 then要领。

瑕玷:

  1. Promise一旦新建它就会马上实行,没法半途作废。
  2. 假如不设置回调函数,Promise内部抛出的毛病,不会回响反映到外部。
  3. 当处于Pending状况时,没法得知现在希望到哪个阶段(刚刚最先照样行将完成)。

基础用法

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的回调函数将会马上实行。

Promise.prototype.then()

the()要领返回一个新的Promise。因而能够采纳链式写法。

promise.then(onFulfilled, onRejected);
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then要领能够吸收两个回调函数作为参数。第一个回调函数是Promise对象的状况变成Resolved时挪用,第二个回调函数是Promise对象的状况变成Reject时挪用。这两个函数都吸收Promise对象传出的值作为参数。若省略这两个参数,或许供应非函数,不会发生任何毛病。

注重:

  • 假如 onFulfilled 或许 onRejected 抛出一个毛病,或许返回一个谢绝的 Promise ,then 返回一个 rejected Promise。
  • 假如 onFulfilled 或许 onRejected 返回一个 resolves Promise,或许返回任何其他值,或许未返回值,then 返回一个 resolved 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()

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()

Promise.all(iterable):当在可迭代参数中的一切promises被resolve,或许任一 Promise 被 reject时,返回一个新的promise。
iterable:一个可迭代对象,比方 Array。

状况剖断

(1)iterable为空(比方[]),返回一个同步的resolved Promise。
(2)iterable未包括任何的promises(比方[1,2,3]),返回一个异步的resolved Promise。
《ES6进修笔记3-Set和Map、Promise、Iterator、Generator、async 、Class》
(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()

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.resolve返回一个Promise对象。

Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);

Promise.resolve要领的参数:

  1. 参数是一个Promise实例:Promise.resolve将不做任何修正、一成不变地返回这个实例。
  2. 参数是一个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。
  3. 其他状况:Promise.resolve要领返回一个新的Promise对象,状况为Resolved。Promise.resolve要领的参数,会同时传给回调函数。

    var p = Promise.resolve('Hello');
    
    p.then(function (s){
    console.log(s)
    });
    // Hello
    //返回Promise实例的状况从一天生就是Resolved,所以回调函数会马上实行

Promise.reject()

Promise.reject(reason)要领也会返回一个新的 Promise 实例,该实例的状况为rejected。因而,回调函数会马上实行。

Promise.reject(reason);

Promise.reject()要领的参数,会一成不变地作为返回的新Promise的[[PromiseValue]]值,变成后续要领的参数。

Iterator(遍历器)

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接口的场所

  1. 解构赋值:对数组和Set构造举行解构赋值时,会默许挪用Symbol.iterator要领。
  2. 扩大运算符:扩大运算符(…)也会挪用默许的iterator接口。因而,可经由过程(…)轻易的将布置了Iterator接口的数据接口转为数组。

     let arr = [...iterable];
  3. yield*:yield*背面跟的是一个可遍历的构造,它会挪用该构造的遍历器接口。
  4. 任何吸收数组作为参数的场所,都挪用了遍历器接口。

     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轮回时:

  1. 遍历的递次是根据各个成员被增加进数据构造的递次。
  2. Set 构造遍用时,返回的是一个值,而 Map 构造遍用时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。

ES6的数组、Set、Map均有以下要领(返回的都是遍历器对象,与Object的entries、keys、values要领差别,Object返回的均是数组。):

  • entries() 返回一个遍历器对象,用来遍历[键名, 键值]构成的数组。关于数组,键名就是索引值;关于 Set,键名与键值雷同。Map 构造的 Iterator 接口,默许就是挪用entries要领。
  • keys() 返回一个遍历器对象,用来遍历一切的键名。
  • values() 返回一个遍历器对象,用来遍历一切的键值。

for…of轮回能准确辨认字符串中的32位 UTF-16 字符。
可经由过程Array.from要领将相似数组的对象转为数组。

与其他遍历语法的比较

  • forEach:没法半途跳出forEach轮回,break敕令或return敕令都不能见效。
  • for…in:不仅遍历数字键名,还会遍历手动增加的其他键,以至包括原型链上的键。

for…of轮回能够与break、continue和return合营运用,供应了遍历一切数据构造的一致操纵接口。

Generator

Generator 函数是一个平常函数,有以下特性:

  1. function症结字与函数名之间有一个星号。
  2. 函数体内部运用yield表达式,定义差别的内部状况。

挪用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"

yield 表达式

遍历器对象的next要领的运转逻辑:

  1. 碰到yield表达式,就停息实行背面的操纵,并将紧跟在yield背面的谁人表达式的值,作为返回的对象的value属性值。
  2. 下一次挪用next要领时,再继续往下实行,直到碰到下一个yield表达式。
  3. 假如没有再碰到新的yield表达式,就一向运转到函数完毕,直到return语句为止,并将return语句背面的表达式的值,作为返回的对象的value属性值。
  4. 假如该函数没有return语句,则返回的对象的value属性值为undefined。
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表达式假如用在另一个表达式当中,必需放在圆括号内里。假如用作函数参数或放在赋值表达式的右侧,能够不加括号。

与 Iterator 接口的关联

恣意一个对象的Symbol.iterator要领,即是该对象的遍历器天生函数,挪用该函数会返回该对象的一个遍历器对象。因为 Generator 函数就是遍历器天生函数,因而能够把 Generator 赋值给对象的Symbol.iterator属性。

Generator 函数实行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,实行后返回本身。

function* gen(){
  // some code
}
var g = gen();
g[Symbol.iterator]() === g
// true

next 要领的参数

yield表达式本身没有返回值,或许说老是返回undefined。next要领能够带一个参数,该参数就会被看成上一个yield表达式的返回值。注重,因为next要领的参数示意上一个yield表达式的返回值,所以第一次运用next要领时,不必带参数。

for…of 轮回

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();
}

Generator.prototype.throw()

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.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* 表达式

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函数

假如一个对象的属性是 Generator 函数,则需在属性前面加一个星号。

let obj = {
  * myGeneratorMethod() {
    ···
  }
};
//等同于
let obj = {
  myGeneratorMethod: function* () {
    // ···
  }
};

Generator 函数的this

Generator 函数老是返回一个遍历器,ES6 划定这个遍历器是 Generator 函数的实例,也继续了 Generator 函数的prototype对象上的要领。Generator函数不能跟new敕令一升引,会报错。

function* g() {}

g.prototype.hello = function () {
  return 'hi!';
};

let obj = g();
obj.hello() // 'hi!'

《ES6进修笔记3-Set和Map、Promise、Iterator、Generator、async 、Class》

上面代码能够看出,obj对象是Generator 函数g的实例。然则,假如把g看成平常的组织函数,并不会见效,因为g返回的老是遍历器对象,而不是this对象。

function* g() {
  this.a = 11;
}

let obj = g();
obj.a // undefined

上面代码中,Generator函数g在this对象上面增加了一个属性a,然则obj对象拿不到这个属性。

运用场景

  1. 用来处置惩罚异步操纵,改写回调函数。即把异步操纵写在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 操纵。
    
  2. 掌握流治理
  3. 应用 Generator 函数,在恣意对象上布置 Iterator 接口。
  4. 作为数据构造。

Generator 函数的异步运用

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 函数

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 敕令

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要领的回调函数被挪用,它的参数就是抛出的毛病对象。

运用注重点

  1. 最好把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);
        };
      }
  2. 多个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);
    }
  3. await敕令只能用在async函数当中,假如用在平常函数,就会报错。

    async function dbFuc(db) {
      let docs = [{}, {}, {}];
    
      // 报错。 因为await用在平常函数当中
      docs.forEach(function (doc) {
        await db.post(doc);
      });
    }

async 函数的完成道理

async 函数的完成道理,就是将 Generator 函数和自动实行器,包装在一个函数里。

异步遍历的接口

异步遍历器的最大的语法特性,就是挪用遍历器的next要领,返回的是一个 Promise 对象。

   asyncIterator
  .next()
  .then(
    ({ value, done }) => /* ... */
  );

上面代码中,asyncIterator是一个异步遍历器,挪用next要领今后,返回一个 Promise 对象。因而,能够运用then要领指定,这个 Promise 对象的状况变成resolve今后的回调函数。回调函数的参数,则是一个具有value和done两个属性的对象,这个跟同步遍历器是一样的。

一个对象的同步遍历器的接口,布置在Symbol.iterator属性上面。同样地,对象的异步遍历器接口,布置在Symbol.asyncIterator属性上面。不论是什么样的对象,只需它的Symbol.asyncIterator属性有值,就示意应当对它举行异步遍历。

for await…of

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函数

在语法上,异步 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属性。

Class

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要领

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 {}

Class表达式

类也能够运用表达式的情势定义。

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症结字,这个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指定运转形式。只需你的代码写在类或模块当中,就只需严厉形式可用。

name属性

本质上,ES6的类只是ES5的组织函数的一层包装,所以函数的很多特性都被Class继续,包括name属性。name属性老是返回紧跟在class症结字背面的类名。

class Point {}
Point.name // "Point"

Class的继续

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);
}

类的prototype属性和__proto__属性

Class同时有prototype属性和__proto__属性,因而同时存在两条继续链。

  1. 子类的__proto__属性,示意组织函数的继续,老是指向父类。
  2. 子类prototype属性的__proto__属性,老是指向父类的prototype属性。

类的继续是根据下面的形式完成的。

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 的继续目的

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要领能够用来从子类上猎取父类。因而,能够运用这个要领推断,一个类是不是继续了另一个类。

Object.getPrototypeOf(ColorPoint) === Point //true

super 症结字

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__属性,指向父类实例的__proto__属性。

class Point{

}
class ColorPoint extends Point{
   constructor(){
      super();
  }
}
var p1 = new Point();
var p2 = new ColorPoint();
p2.__proto__.__proto__ === p1.__proto__ // true

原生组织函数的继续

原生组织函数是指言语内置的组织函数,一般用来天生数据构造。ECMAScript的原生组织函数大抵有下面这些。

  • Boolean()
  • Number()
  • String()
  • Array()
  • Date()
  • Function()
  • RegExp()
  • Error()
  • Object()

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的取值函数(getter)和存值函数(setter)

在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属性有对应的存值函数和取值函数,因而赋值和读取行动都被自定义了。

Class的静态要领

在一个要领前,加上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本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。因为ES6明确划定,Class内部只需静态要领,没有静态属性。所以现在只需下面这类写法。

//为Foo类定义了一个静态属性prop
class Foo {
}

Foo.prop = 1;
Foo.prop // 1

ES7有一个静态属性的提案,现在Babel转码器支撑。这个提案划定:

  1. 类的实例属性能够用等式,写入类的定义当中。

    class MyClass {
      myProp = 42;
    
      constructor() {
        console.log(this.myProp); // 42
      }
    }
  2. 类的静态属性只需在上面的实例属性写法前面,加上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())。

new.target属性

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 入门

    原文作者:lijinxieyang
    原文地址: https://segmentfault.com/a/1190000009643856
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞