let s = Symbol();
console.log(s); // Symbol()
typeof s; // "symbol"
複製代碼
let s = new Symbol();
// Uncaught TypeError: Symbol is not a constructor(…)
複製代碼
let s1 = Symbol('s1');
let s2 = Symbol('s2');
console.log(s1); // Symbol(s1)
console.log(s2); // Symbol(s2)
s1 === s2; // false
let s3 = Symbol('s2');
s2 === s3; // false
複製代碼
給Symbol函數加了參數以後,控制檯輸出的時候能夠區分究竟是哪個值。Symbol函數的參數只是對當前Symbol值的描述,所以相同參數的Symbol函數返回值是不相等的。html
var mysym1 = Symbol('my symbol');
mysym1.toString() // 'Symbol('my symbol')'
String(mysym1) // 'Symbol('my symbol')'
var mysym2 = Symbol();
Boolean(mysym2); // true
Number(mysym2) // TypeError: Cannot convert a Symbol value to a number(…)
複製代碼
let a = {};
let s4 = Symbol();
// 第一種寫法
a[s4] = 'mySymbol';
// 第二種寫法
a = {
[s4]: 'mySymbol'
}
// 第三種寫法
Object.defineProperty(a, s4, {value: 'mySymbol1'});
a.s4; // undefined
a.s4 = 'mySymbol2';
a[s4] // mySymbol1
a['s4'] // 'mySymbol2'
複製代碼
使用對象的Symbol值做爲屬性名時,獲取相應的屬性值不能用點運算符; 若是用點運算符來給對象的屬性賦Symbol類型的值,實際上屬性名會變成一個字符串,而不是一個Symbol值; 在對象內部,使用Symbol值定義屬性時,Symbol值必須放在方括號之中,不然只是一個字符串。 6. Symbol值做爲屬性名的遍歷。 使用for...in和for...of都沒法遍歷到Symbol值的屬性,Symbol值做爲對象的屬性名,也沒法經過Object.keys()、Object.getOwnPropertyNames()來獲取了。可是,不一樣擔憂,這種日常的需求確定是會有解決辦法的。咱們可使用Object.getOwnPropertySymbols()方法獲取一個對象上的Symbol屬性名。也可使用Reflect.ownKeys()返回全部類型的屬性名,包括常規屬性名和 Symbol屬性名。git
let s5 = Symbol('s5');
let s6 = Symbol('s6');
let a = {
[s5]: 's5',
[s6]: 's6'
}
Object.getOwnPropertySymbols(a); // [Symbol(s5), Symbol(s6)]
a.hello = 'hello';
Reflect.ownKeys(a); // ["hello", Symbol(s5), Symbol(s6)]
複製代碼
利用Symbol值做爲對象屬性的名稱時,不會被常規方法遍歷到這一特性,能夠爲對象定義一些非私有的可是又但願只有內部可用的方法。 7. Symbol.for()和Symbol.keyFor()。 Symbol.for()函數也能夠用來生成Symbol值,但該函數有一個特殊的用處,就是能夠重複使用一個Symbol值。es6
let s1 = Symbol.for("s11");
let s2 = Symbol.for("s22");
console.log(s1===s2)//false
let s3 = Symbol("s33");
let s4 = Symbol("s33");
console.log(s3===s4)//false
console.log(Symbol.keyFor(s3))//undefined
console.log(Symbol.keyFor(s2))//"s22"
console.log(Symbol.keyFor(s1))//"s11"
複製代碼
Symbol.for()
函數要接受一個字符串做爲參數,先搜索有沒有以該參數做爲名稱的Symbol值,若是有,就直接返回這個Symbol值,不然就新建並返回一個以該字符串爲名稱的Symbol值。 Symbol.keyFor()
函數是用來查找一個Symbol值的登記信息的,Symbol()寫法沒有登記機制,因此返回undefined;而Symbol.for()函數會將生成的Symbol值登記在全局環境中,因此Symbol.keyFor()函數能夠查找到用Symbol.for()函數生成的Symbol值。 8. 內置Symbol值。 ES6提供了11個內置的Symbol值,分別是Symbol.hasInstance 、Symbol.isConcatSpreadable 、Symbol.species 、Symbol.match 、Symbol.replace 、Symbol.search 、Symbol.split 、Symbol.iterator 、Symbol.toPrimitive 、Symbol.toStringTag 、Symbol.unscopables 等。github
const s = new 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
//經過add方法向 Set 結構加入成員,結果代表 Set 結構不會添加劇復的值。
複製代碼
Set 函數能夠接受一個數組做爲參數,用來初始化。算法
// 例一
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
// 例三
function divs () {
return [...document.querySelectorAll('div')];
}
const set = new Set(divs());
set.size // 56
// 相似於
divs().forEach(div => set.add(div));
set.size // 56
複製代碼
利用Set數據結構的成員都是惟一的這個特性,能夠輕鬆對數組去重。編程
// 去除數組的重複成員
[...new Set(array)]
複製代碼
向 Set 加入值的時候,不會發生類型轉換,因此5
和"5"
是兩個不一樣的值。 Set 內部判斷兩個值是否不一樣,使用的算法叫作「Same-value equality」,它相似於精確相等運算符(===
),主要的區別是NaN
等於自身,而精確相等運算符認爲NaN
不等於自身。 兩個對象老是不相等的。 Set 結構的實例有如下屬性:json
Set.prototype.constructor
:構造函數,默認就是Set
函數。Set.prototype.size
:返回Set
實例的成員總數。 Set 實例的方法分爲兩大類:操做方法(用於操做數據)和遍歷方法(用於遍歷成員)。四個操做方法:數組
add(value)
:添加某個值,返回 Set 結構自己。delete(value)
:刪除某個值,返回一個布爾值,表示刪除是否成功。has(value)
:返回一個布爾值,表示該值是否爲Set
的成員。clear()
:清除全部成員,沒有返回值。 Array.from()能夠將 Set 結構轉爲數組。const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
複製代碼
Set 結構的實例有四個遍歷方法,能夠用於遍歷成員。promise
keys()
:返回鍵名的遍歷器values()
:返回鍵值的遍歷器entries()
:返回鍵值對的遍歷器forEach()
:使用回調函數遍歷每一個成員 keys
方法、values
方法、entries
方法返回的都是遍歷器對象。因爲 Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),因此keys
方法和values
方法的行爲徹底一致。let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
複製代碼
Set 結構的實例默承認遍歷,它的默認遍歷器生成函數就是它的values
方法。 Set 結構的實例與數組同樣,也擁有forEach
方法,用於對每一個成員執行某種操做,沒有返回值。瀏覽器
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9
複製代碼
forEach
方法還能夠有第二個參數,表示綁定處理函數內部的this
對象。 2. WeakSet WeakSet 結構與 Set 相似,也是不重複的值的集合。可是,它與 Set 有兩個區別:
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
//下面的寫法不行
const b = [3, 4];
const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)
複製代碼
WeakSet 結構有如下三個方法。
WeakSet.prototype.add(value)
:向 WeakSet 實例添加一個新成員。WeakSet.prototype.delete(value)
:清除 WeakSet 實例的指定成員。WeakSet.prototype.has(value)
:返回一個布爾值,表示某個值是否在 WeakSet 實例之中。 WeakSet 沒有size
屬性,沒有辦法遍歷它的成員。const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
複製代碼
做爲構造函數,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"
複製代碼
只有對同一個對象的引用,Map 結構纔將其視爲同一個鍵。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
複製代碼
若是 Map 的鍵是一個簡單類型的值(數字、字符串、布爾值),則只要兩個值嚴格相等,Map 將其視爲一個鍵,好比0
和-0
就是一個鍵,布爾值true
和字符串true
則是兩個不一樣的鍵。另外,undefined
和null
也是兩個不一樣的鍵。雖然NaN
不嚴格相等於自身,但 Map 將其視爲同一個鍵。
實例的屬性和操做方法
keys()
:返回鍵名的遍歷器。values()
:返回鍵值的遍歷器。entries()
:返回全部成員的遍歷器。forEach()
:遍歷 Map 的全部成員。 Map 的遍歷順序就是插入順序。遍歷行爲基本與set的一致。 Map能夠轉爲數組。const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap] //[[true, 7], [{foo: 3},["abc"]]]
複製代碼
數組也能夠轉爲Map
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
複製代碼
若是全部 Map 的鍵都是字符串,它能夠轉爲對象。
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
複製代碼
對象也能夠轉爲Map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
複製代碼
Map能夠轉爲JSON,可是要分兩種狀況。 一種狀況是,Map 的鍵名都是字符串,這時能夠選擇轉爲對象 JSON。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
複製代碼
另外一種狀況是,Map 的鍵名有非字符串,這時能夠選擇轉爲數組 JSON。
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
複製代碼
JSON能夠轉爲Map 正常狀況下,全部鍵名都是字符串。
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
複製代碼
可是,有一種特殊狀況,整個 JSON 就是一個數組,且每一個數組成員自己,又是一個有兩個成員的數組。這時,它能夠一一對應地轉爲 Map。這每每是數組轉爲 JSON 的逆操做。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
複製代碼
WeakMap
結構與Map
結構相似,也是用於生成鍵值對的集合。 WeakMap
與Map
的區別有兩點:WeakMap
只接受對象做爲鍵名(null
除外),不接受其餘類型的值做爲鍵名。WeakMap
的鍵名所指向的對象,不計入垃圾回收機制。 WeakMap
的設計目的在於,有時咱們想在某個對象上面存放一些數據,可是這會造成對於這個對象的引用。const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
[e1, 'foo 元素'],
[e2, 'bar 元素'],
];
//e1和e2是兩個對象,咱們經過arr數組對這兩個對象添加一些文字說明。這就造成了arr對e1和e2的引用。
//一旦再也不須要這兩個對象,咱們就必須手動刪除這個引用,不然垃圾回收機制就不會釋放e1和e2佔用的內存。
複製代碼
WeakMap 就是爲了解決這個問題而誕生的,它的鍵名所引用的對象都是弱引用,即垃圾回收機制不將該引用考慮在內。
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
複製代碼
WeakMap
只有四個方法可用:get()
、set()
、has()
、delete()
。 沒法被遍歷,由於沒有size。沒法被清空,由於沒有clear(),跟WeakSet類似。
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 節點刪除,該狀態就會自動消失,不存在內存泄漏風險。
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
//上面代碼對一個空對象架設了一層攔截,重定義了屬性的讀取(get)和設置(set)行爲
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
複製代碼
上面代碼說明,Proxy 實際上重載(overload)了點運算符,即用本身的定義覆蓋了語言的原始定義。 ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。
let proxy = new Proxy(target, handler);
複製代碼
Proxy 對象的全部用法,都是上面這種形式,不一樣的只是handler
參數的寫法。其中,new Proxy()
表示生成一個Proxy
實例,target
參數表示所要攔截的目標對象,handler
參數也是一個對象,用來定製攔截行爲。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
複製代碼
若是handler
沒有設置任何攔截,那就等同於直接通向原對象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
複製代碼
上面代碼中,handler
是一個空對象,沒有任何攔截效果,訪問proxy
就等同於訪問target
。 同一個攔截器函數,能夠設置攔截多個操做。 對於能夠設置、但沒有設置攔截的操做,則直接落在目標對象上,按照原先的方式產生結果。 下面是 Proxy 支持的攔截操做一覽,一共 13 種:
proxy.foo
和proxy['foo']
。proxy.foo = v
或proxy['foo'] = v
,返回一個布爾值。propKey in proxy
的操做,返回一個布爾值。delete proxy[propKey]
的操做,返回一個布爾值。Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
,返回一個數組。該方法返回目標對象全部自身的屬性的屬性名,而Object.keys()
的返回結果僅包括目標對象自身的可遍歷屬性。Object.getOwnPropertyDescriptor(proxy, propKey)
,返回屬性的描述對象。Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一個布爾值。Object.preventExtensions(proxy)
,返回一個布爾值。Object.getPrototypeOf(proxy)
,返回一個對象。Object.isExtensible(proxy)
,返回一個布爾值。Object.setPrototypeOf(proxy, proto)
,返回一個布爾值。若是目標對象是函數,那麼還有兩種額外操做能夠攔截。proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。new proxy(...args)
。 deleteProperty
方法用於攔截delete
操做,若是這個方法拋出錯誤或者返回false
,當前屬性就沒法被delete
命令刪除。 apply
方法攔截函數的調用、call
和apply
操做。 get
方法用於攔截某個屬性的讀取操做。let obj2 = new Proxy(obj,{
get(target,property,a){
//return 35;
/*console.log(target) console.log(property)*/
let Num = ++wkMap.get(obj).getPropertyNum;
console.log(`當前訪問對象屬性次數爲:${Num}`)
return target[property]
},
deleteProperty(target,property){
return false;
},
apply(target,ctx,args){
return Reflect.apply(...[target,[],args]);;
}
})
複製代碼
Proxy.revocable
方法返回一個可取消的 Proxy 實例。let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
複製代碼
Proxy.revocable
方法返回一個對象,該對象的proxy
屬性是Proxy
實例,revoke
屬性是一個函數,能夠取消Proxy
實例。上面代碼中,當執行revoke
函數以後,再訪問Proxy
實例,就會拋出一個錯誤。 Proxy.revocable
的一個使用場景是,目標對象不容許直接訪問,必須經過代理訪問,一旦訪問結束,就收回代理權,不容許再次訪問。 3. this問題。 雖然 Proxy 能夠代理針對目標對象的訪問,但它不是目標對象的透明代理,即不作任何攔截的狀況下,也沒法保證與目標對象的行爲一致。主要緣由就是在 Proxy 代理的狀況下,目標對象內部的this
關鍵字會指向 Proxy 代理。
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
//一旦proxy代理target.m,後者內部的this就是指向proxy,而不是target。
複製代碼
Reflect
對象與Proxy
對象同樣,也是 ES6 爲了操做對象而提供的新 API。 設計目的:
Object
對象的一些明顯屬於語言內部的方法(好比Object.defineProperty
),放到Reflect
對象上。現階段,某些方法同時在Object
和Reflect
對象上部署,將來的新方法將只部署在Reflect
對象上。Object
方法的返回結果,讓其變得更合理。好比,Object.defineProperty(obj, name, desc)
在沒法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)
則會返回false
。Object
操做都變成函數行爲。某些Object
操做是命令式,好比name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
讓它們變成了函數行爲。Reflect
對象的方法與Proxy
對象的方法一一對應,只要是Proxy
對象的方法,就能在Reflect
對象上找到對應的方法。這就讓Proxy
對象能夠方便地調用對應的Reflect
方法,完成默認行爲,做爲修改行爲的基礎。也就是說,無論Proxy
怎麼修改默認行爲,你總能夠在Reflect
上獲取默認行爲。Promise
對象表明一個異步操做,有三種狀態: pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。 只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。Promise
,一旦新建它就會當即執行,沒法中途取消。Promise
內部拋出的錯誤,不會反應到外部。pending
狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。let p = new Promise((resolve,reject)=>{
//一些異步操做
setTimeout(()=>{
console.log("123")
resolve("abc");
},0)
})
.then(function(data){
//resolve狀態
console.log(data)
},function(err){
//reject狀態
})
//'123'
//'abc'
複製代碼
Promise
實例生成之後,能夠用then
方法分別指定resolved
狀態和rejected
狀態的回調函數。 也就是說,狀態由實例化時的參數(函數)執行來決定的,根據不一樣的狀態,看看須要走then的第一個參數仍是第二個。 resolve()和reject()的參數會傳遞到對應的回調函數的data或err。 6. 鏈式操做的用法。 從表面上看,Promise只是可以簡化層層回調的寫法,而實質上,Promise的精髓是「狀態」,用維護狀態、傳遞狀態的方式來使得回調函數可以及時調用,它比傳遞callback函數要簡單、靈活的多。因此使用Promise的正確場景是這樣的:
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return runAsync3();
})
.then(function(data){
console.log(data);
});
//異步任務1執行完成
//隨便什麼數據1
//異步任務2執行完成
//隨便什麼數據2
//異步任務3執行完成
//隨便什麼數據3
function runAsync1(){
var p = new Promise(function(resolve, reject){
//作一些異步操做
setTimeout(function(){
console.log('異步任務1執行完成');
resolve('隨便什麼數據1');
}, 1000);
});
return p;
}
function runAsync2(){
var p = new Promise(function(resolve, reject){
//作一些異步操做
setTimeout(function(){
console.log('異步任務2執行完成');
resolve('隨便什麼數據2');
}, 2000);
});
return p;
}
function runAsync3(){
var p = new Promise(function(resolve, reject){
//作一些異步操做
setTimeout(function(){
console.log('異步任務3執行完成');
resolve('隨便什麼數據3');
}, 2000);
});
return p;
}
複製代碼
在then方法中,你也能夠直接return數據而不是Promise對象,在後面的then中也能夠接收到數據:
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return '直接返回數據'; //這裏直接返回數據
})
.then(function(data){
console.log(data);
});
//異步任務1執行完成
//隨便什麼數據1
//異步任務2執行完成
//隨便什麼數據2
//直接返回數據
複製代碼
let num = 10;
let p1 = function() {
return new Promise((resolve,reject)=>{
if (num <= 5) {
resolve("<=5,走resolce")
console.log('resolce不能結束Promise')
}else{
reject(">5,走reject")
console.log('reject不能結束Promise')
}
})
}
p1()
.then(function(data){
console.log(data)
},function(err){
console.log(err)
})
//reject不能結束Promise
//>5,走reject
複製代碼
resolve和reject永遠會在當前環境的最後執行,因此後面的同步代碼會先執行。 若是resolve和reject以後還有代碼須要執行,最好放在then裏。 而後在resolve和reject前面寫上return。 8. Promise.prototype.catch
。 Promise.prototype.catch
方法是.then(null, rejection)
的別名,用於指定發生錯誤時的回調函數。
p1()
.then(function(data){
console.log(data)
})
.catch(function(err){
console.log(err)
})
//reject不能結束Promise
//>5,走reject
複製代碼
Promise.all()
。 Promise.all
方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。const p = Promise.all([p1, p2, p3]);
複製代碼
p
的狀態由p1
、p2
、p3
決定,分紅兩種狀況。
p1
、p2
、p3
的狀態都變成resolve
,p
的狀態纔會變成resolve
。 此時p1
、p2
、p3
的返回值組成一個數組,傳遞給p
的回調函數。p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成rejected
,此時第一個被reject
的實例的返回值,會傳遞給p
的回調函數。 promises
是包含 3 個 Promise 實例的數組,只有這 3 個實例的狀態都變成resolve
,或者其中有一個變爲rejected
,纔會調用Promise.all
方法後面的回調函數。 若是做爲參數的 Promise 實例,本身定義了catch
方法,那麼它一旦被rejected
,並不會觸發Promise.all()
的catch
方法,若是沒有參數沒有定義本身的catch,就會調用Promise.all()
的catch
方法。Promise.race
。 Promise.race
方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。const p = Promise.race([p1, p2, p3]);
複製代碼
上面代碼中,只要p1
、p2
、p3
之中有一個實例率先改變狀態,p
的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p
的回調函數。 11. Promise.resolve()
。 有時須要將現有對象轉爲 Promise 對象,Promise.resolve
方法就起到這個做用。下面代碼將123轉爲一個 Promise 對象。
const jsPromise = Promise.resolve('123');
複製代碼
Promise.resolve
等價於下面的寫法。
Promise.resolve('123')
// 等價於
new Promise(resolve => resolve('123'))
複製代碼
Promise.resolve
方法的參數分紅四種狀況。
Promise.resolve
將不作任何修改、原封不動地返回這個實例。thenable
對象指的是具備then
方法的對象,好比下面這個對象。let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
複製代碼
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。
then
方法的對象,則Promise.resolve
方法返回一個新的 Promise 對象,狀態爲resolved
。const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
複製代碼
上面代碼生成一個新的 Promise 對象的實例p
。因爲字符串Hello
不屬於異步操做(判斷方法是字符串對象不具備 then 方法),返回 Promise 實例的狀態從一輩子成就是resolved
,因此回調函數會當即執行。Promise.resolve
方法的參數,會同時傳給回調函數。
Promise.resolve
方法容許調用時不帶參數,直接返回一個resolved
狀態的 Promise 對象。 因此,若是但願獲得一個 Promise 對象,比較方便的方法就是直接調用Promise.resolve
方法。const p = Promise.resolve();
p.then(function () {
// ...
});
複製代碼
上面代碼的變量p
就是一個 Promise 對象。 須要注意的是,當即resolve
的 Promise 對象,是在本輪「事件循環」(event loop)的結束時,而不是在下一輪「事件循環」的開始時。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
複製代碼
上面代碼中,setTimeout(fn, 0)
在下一輪「事件循環」開始時執行,Promise.resolve()
在本輪「事件循環」結束時執行,console.log('one')
則是當即執行,所以最早輸出。 12. Promise.reject()
。 Promise.reject(reason)
方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected
。
const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
console.log(s)
});
// 出錯了
複製代碼
上面代碼生成一個 Promise 對象的實例p
,狀態爲rejected
,回調函數會當即執行。 注意,Promise.reject()
方法的參數,會原封不動地做爲reject
的理由,變成後續方法的參數。這一點與Promise.resolve
方法不一致。
const thenable = {
then(resolve, reject) {
reject('出錯了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
複製代碼
上面代碼中,Promise.reject
方法的參數是一個thenable
對象,執行之後,後面catch
方法的參數不是reject
拋出的「出錯了」這個字符串,而是thenable
對象。
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function(){
console.log(xhr.response);
};
xhr.onerror = function(){
console.log("Oops, error");
};
xhr.send();
複製代碼
使用Fetch後:
fetch(url)
.then(function(response){
return response.json();
}).then(function(data){
console.log(data);
}).catch(function(e){
console.log("Oops, error");
});
複製代碼
使用ES6的箭頭函數後:
fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("Oops, error", e))
複製代碼
使用async/await來作最終優化:
try{
let response = await fetch(url);
let data = await response.json();
console.log(data);
}catch(e){
console.log("Oops, error", e);
}
//注:這段代碼若是想運行,外面須要包一個async function
複製代碼
fetch(url, options).then(function(response){
//handle HTTP response
}, function(error){
//handle network error
})
複製代碼
url:定義要獲取的資源。這多是:
USVString
字符串,包含要獲取資源的URL。Request
對象。 options(可選) 一個配置項對象,包括全部對請求的設置,可選的參數有:method
:請求使用的方法,如GET、POST。headers
:請求的頭信息,形式爲Headers對象或ByteString。body
:請求的body信息,多是一個Blob、BufferSource、FormData、URLSearchParams或者USVString對象。注意GET或HEAD方法的請求不能包含body信息。mode
:請求的模式,如cors、no-cors或者same-origin。credentials
:請求的credentials,如omit、same-origin或者include。cache
:請求的cache模式:default,no-store,reload,no-cache,force-cache,或者only-if-cached。response: 一個Promise,resolve時回傳Response
對象: 屬性:
fetch('/users.html')
.then(function(response){
return response.text();
}).then(function(body){
document.body.innerHTML = body
})
複製代碼
post
fetch('/users', {
method:'POST',
headers:{
'Accept':'application/json',
'Content-Type':'application/json'
},
body:JSON.stringify({
name:'Hubot',
login:'hubot',
})
})
複製代碼
迭代器是一種接口、是一種機制。 爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。 Iterator 的做用有三個:
for...of
消費。 Iterator本質上,就是一個指針對象。 過程是這樣的: (1)建立一個指針對象,指向當前數據結構的起始位置。 (2)第一次調用指針對象的next
方法,能夠將指針指向數據結構的第一個成員。 (3)第二次調用指針對象的next
方法,指針就指向數據結構的第二個成員。 (4)不斷調用指針對象的next
方法,直到它指向數據結構的結束位置。 普通函數實現Iterator。function myIter(obj){
let i = 0;
return {
next(){
let done = (i>=obj.length);
let value = !done ? obj[i++] : undefined;
return {
value,
done,
}
}
}
}
複製代碼
原生具有 Iterator 接口的數據結構以下:Array、Map、Set、String、函數的arguments對象、NodeList對象。 下面的例子是數組的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 }
複製代碼
下面是另外一個相似數組的對象調用數組的Symbol.iterator
方法的例子。
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
複製代碼
注意,普通對象部署數組的Symbol.iterator
方法,並沒有效果。
let iterable = {
a: 'a',
b: 'b',
c: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // undefined, undefined, undefined
}
複製代碼
字符串是一個相似數組的對象,也原生具備 Iterator 接口。
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
複製代碼
function
關鍵字與函數名之間有一個星號;yield
表達式,定義不一樣的內部狀態。function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
複製代碼
上面代碼定義了一個 Generator 函數helloWorldGenerator
,它內部有兩個yield
表達式(hello
和world
),即該函數有三個狀態:hello,world 和 return 語句(結束執行)。 調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,也就是遍歷器對象。 下一步,必須調用遍歷器對象的next
方法,使得指針移向下一個狀態。也就是說,每次調用next
方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield
表達式(或return
語句)爲止。換言之,Generator 函數是分段執行的,yield
表達式是暫停執行的標記,而next
方法能夠恢復執行。 ES6 沒有規定,function
關鍵字與函數名之間的星號,寫在哪一個位置。這致使下面的寫法都能經過。
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
複製代碼
next
方法纔會遍歷下一個內部狀態,因此其實提供了一種能夠暫停執行的函數。yield
表達式就是暫停標誌。 遍歷器對象的next
方法的運行邏輯以下。 (1)遇到yield
表達式,就暫停執行後面的操做,並將緊跟在yield
後面的那個表達式的值,做爲返回的對象的value
屬性值。 (2)下一次調用next
方法時,再繼續往下執行,直到遇到下一個yield
表達式。 (3)若是沒有再遇到新的yield
表達式,就一直運行到函數結束,直到return
語句爲止,並將return
語句後面的表達式的值,做爲返回的對象的value
屬性值。 (4)若是該函數沒有return
語句,則返回的對象的value
屬性值爲undefined
。 yield表達式與return語句的相同之處: 都能返回緊跟在語句後面的那個表達式的值。 yield表達式與return語句的不一樣之處: 每次遇到yield
,函數暫停執行,下一次再從該位置繼續向後執行,而return
語句不具有位置記憶的功能。一個函數裏面,只能執行一次(或者說一個)return
語句,可是能夠執行屢次(或者說多個)yield
表達式。正常函數只能返回一個值,由於只能執行一次return
;Generator 函數能夠返回一系列的值,由於能夠有任意多個yield
。 yield
表達式只能用在 Generator 函數裏面,用在其餘地方都會報錯。 另外,yield
表達式若是用在另外一個表達式之中,必須放在圓括號裏面。console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield 123)); // OK
複製代碼
Symbol.iterator
屬性,從而使得該對象具備 Iterator 接口。Object.prototype[Symbol.iterator] = function* (){
for(let i in this){
yield this[i];
}
}
//--------------
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
複製代碼
yield
表達式自己沒有返回值,或者說老是返回undefined
。next
方法能夠帶一個參數,該參數就會被看成上一個yield
表達式的返回值。function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
複製代碼
Generator 函數從暫停狀態到恢復運行,它的上下文狀態(context)是不變的。經過next
方法的參數,就有辦法在 Generator 函數開始運行以後,繼續向函數體內部注入值。
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
複製代碼
for...of
循環能夠自動遍歷 Generator 函數時生成的Iterator
對象,且此時再也不須要調用next
方法。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
複製代碼
function* fibonacci() {
let [prev, curr] = [1, 1];
while(true){
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (let n of fibonacci()) {
if (n > 10000000) break;
console.log(n);
}
複製代碼
return
方法,能夠返回給定的值,而且終結遍歷 Generator 函數。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 }
複製代碼
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "y"
複製代碼
foo
和bar
都是 Generator 函數,在bar
裏面調用foo
,是不會有效果的。 這個就須要用到yield*
表達式,用來在一個 Generator 函數裏面執行另外一個 Generator 函數。
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"
複製代碼
再來看一個對比的例子。
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一個遍歷器對象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
複製代碼
上面例子中,outer2
使用了yield*
,outer1
沒使用。結果就是,outer1
返回一個遍歷器對象,outer2
返回該遍歷器對象的內部值。 從語法角度看,若是yield
表達式後面跟的是一個遍歷器對象,須要在yield
表達式後面加上星號,代表它返回的是一個遍歷器對象。這被稱爲yield*
表達式。 8. 做爲對象屬性的 Generator 函數 若是一個對象的屬性是 Generator 函數,能夠簡寫成下面的形式。
let obj = {
* myGeneratorMethod() {
···
}
};
複製代碼
async
函數使用時就是將 Generator 函數的星號(*
)替換成async
,將yield
替換成await
,僅此而已。 async
函數對 Generator 函數的區別: (1)內置執行器。 Generator 函數的執行必須靠執行器,而async
函數自帶執行器。也就是說,async
函數的執行,與普通函數如出一轍,只要一行。 (2)更好的語義。 async
和await
,比起星號和yield
,語義更清楚了。async
表示函數裏有異步操做,await
表示緊跟在後面的表達式須要等待結果。 (3)正常狀況下,await
命令後面是一個 Promise 對象。若是不是,會被轉成一個當即resolve
的 Promise 對象。 (4)返回值是 Promise。 async
函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象方便多了。你能夠用then
方法指定下一步的操做。 進一步說,async
函數徹底能夠看做多個異步操做,包裝成的一個 Promise 對象,而await
命令就是內部then
命令的語法糖。await
後面的異步操做出錯,那麼等同於async
函數返回的 Promise 對象被reject
。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
方法的回調函數被調用,它的參數就是拋出的錯誤對象。具體的執行機制,能夠參考後文的「async 函數的實現原理」。 防止出錯的方法,也是將其放在try...catch
代碼塊之中。
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出錯了');
});
} catch(e) {
}
return await('hello world');
}
複製代碼
若是有多個await
命令,能夠統一放在try...catch
結構中。
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
複製代碼
var fn = function (time) {
console.log("開始處理異步");
setTimeout(function () {
console.log(time);
console.log("異步處理完成");
iter.next();
}, time);
};
function* g(){
console.log("start");
yield fn(3000)
yield fn(500)
yield fn(1000)
console.log("end");
}
let iter = g();
iter.next();
複製代碼
下面是async函數的寫法。
var fn = function (time) {
return new Promise(function (resolve, reject) {
console.log("開始處理異步");
setTimeout(function () {
resolve();
console.log(time);
console.log("異步處理完成");
}, time);
})
};
var start = async function () {
// 在這裏使用起來就像同步代碼那樣直觀
console.log('start');
await fn(3000);
await fn(500);
await fn(1000);
console.log('end');
};
start();
複製代碼
class
關鍵字,能夠定義類。 ES6 的class
能夠看做只是一個語法糖,它的絕大部分功能,ES5 均可以作到,新的class
寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。//es5
function Fn(x, y) {
this.x = x;
this.y = y;
}
Fn.prototype.add = function () {
return this.x + this.y;
};
//等價於
//es6
class Fn{
constructor(x,y){
this.x = x;
this.y = y;
}
add(){
return this.x + this.y;
}
}
var F = new Fn(1, 2);
console.log(F.add()) //3
複製代碼
構造函數的prototype
屬性,在 ES6 的「類」上面繼續存在。事實上,類的全部方法都定義在類的prototype
屬性上面。
class Fn {
constructor() {
// ...
}
add() {
// ...
}
sub() {
// ...
}
}
// 等同於
Fn.prototype = {
constructor() {},
add() {},
sub() {},
};
複製代碼
類的內部全部定義的方法,都是不可枚舉的(non-enumerable),這與es5不一樣。
//es5
var Fn = function (x, y) {
// ...
};
Fn.prototype.add = function() {
// ...
};
Object.keys(Fn.prototype)
// ["add"]
Object.getOwnPropertyNames(Fn.prototype)
// ["constructor","add"]
//es6
class Fn {
constructor(x, y) {
// ...
}
add() {
// ...
}
}
Object.keys(Fn.prototype)
// []
Object.getOwnPropertyNames(Fn.prototype)
// ["constructor","add"]
複製代碼
use strict
指定運行模式。只要你的代碼寫在類或模塊之中,就只有嚴格模式可用。 考慮到將來全部的代碼,其實都是運行在模塊之中,因此 ES6 實際上把整個語言升級到了嚴格模式。constructor
方法是類的默認方法,經過new
命令生成對象實例時,自動調用該方法。一個類必須有constructor
方法,若是沒有顯式定義,一個空的constructor
方法會被默認添加。class Fn {
}
// 等同於
class Fn {
constructor() {}
}
複製代碼
constructor
方法默認返回實例對象(即this
),徹底能夠指定返回另一個對象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
//constructor函數返回一個全新的對象,結果致使實例對象不是Foo類的實例。
複製代碼
new
調用,不然會報錯。這是它跟普通構造函數的一個主要區別,後者不用new
也能夠執行。class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
複製代碼
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
複製代碼
上面代碼使用表達式定義了一個類。須要注意的是,這個類的名字是MyClass
而不是Me
,Me
只在 Class 的內部代碼可用,指代當前類。
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
複製代碼
若是類的內部沒用到的話,能夠省略Me
,也就是能夠寫成下面的形式。
const MyClass = class { /* ... */ };
複製代碼
採用 Class 表達式,能夠寫出當即執行的 Class。
let Person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('張三');
Person.sayName(); // "張三"
複製代碼
上面代碼中,person
是一個當即執行的類的實例。 6. 私有方法和私有屬性 私有方法/私有屬性是常見需求,但 ES6 不提供,只能經過變通方法模擬實現。 一般是在命名上加以區別。
class Fn {
// 公有方法
foo () {
//....
}
// 僞裝是私有方法(其實外部仍是能夠訪問)
_bar() {
//....
}
}
複製代碼
static
關鍵字,就表示該方法不會被實例繼承,而是直接經過類來調用,這就稱爲「靜態方法」。 ES6 明確規定,Class 內部只有靜態方法,沒有靜態屬性。class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
//靜態屬性只能手動設置
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
複製代碼
class Fn{
constructor(){
this.arr = []
}
get bar(){
return this.arr;
}
set bar(value){
this.arr.push(value)
}
}
let obj = new Fn();
obj.menu = 1;
obj.menu = 2;
console.log(obj.menu)//[1,2]
console.log(obj.arr)//[1,2]
複製代碼
class Fn {
}
class Fn2 extends Fn {
}
複製代碼
子類必須在constructor
方法中調用super
方法,不然新建實例時會報錯。這是由於子類沒有本身的this
對象,而是繼承父類的this
對象,而後對其進行加工。若是不調用super
方法,子類就得不到this
對象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
// super()//必須調用
}
}
let cp = new ColorPoint(); // ReferenceError
複製代碼
父類的靜態方法也會被繼承。 11. Object.getPrototypeOf() Object.getPrototypeOf
方法能夠用來從子類上獲取父類。
Object.getPrototypeOf(Fn2) === Fn
// true
複製代碼
所以,可使用這個方法判斷,一個類是否繼承了另外一個類。 12. super關鍵字 super
這個關鍵字,既能夠看成函數使用,也能夠看成對象使用。在這兩種狀況下,它的用法徹底不一樣。 第一種狀況,super
做爲函數調用時,表明父類的構造函數。ES6 要求,子類的構造函數必須執行一次super
函數。 做爲函數時,super()
只能用在子類的構造函數之中,用在其餘地方就會報錯。
class A {}
class B extends A {
constructor() {
super();
}
}
複製代碼
上面代碼中,子類B
的構造函數之中的super()
,表明調用父類的構造函數。這是必須的,不然 JavaScript 引擎會報錯。 注意,super
雖然表明了父類A
的構造函數,可是返回的是子類B
的實例,即super
內部的this
指的是B
,所以super()
在這裏至關於A.prototype.constructor.call(this)
。 第二種狀況,super
做爲對象時,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
複製代碼
上面代碼中,子類B
當中的super.p()
,就是將super
看成一個對象使用。這時,super
在普通方法之中,指向A.prototype
,因此super.p()
就至關於A.prototype.p()
。 因爲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
。
export
和import
。 export
命令用於規定模塊的對外接口。 import
命令用於輸入其餘模塊提供的功能。 一個模塊就是一個獨立的文件。該文件內部的全部變量,外部沒法獲取。若是你但願外部可以讀取模塊內部的某個變量,就必須使用export
關鍵字輸出該變量。 export輸出變量的寫法:// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
複製代碼
還能夠一塊兒導出。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
//跟上面寫法等價,推薦這種寫法。
複製代碼
export
命令除了輸出變量,還能夠輸出函數或類(class)。
export function multiply(x, y) {
return x * y;
};
複製代碼
一般狀況下,export
輸出的變量就是原本的名字,可是可使用as
關鍵字重命名。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
複製代碼
export
命令規定的是對外的接口,必須與模塊內部的變量創建一一對應關係。
// 報錯
export 1;
// 報錯
var m = 1;
export m;
//正確寫法
// 寫法一
export var m = 1;
// 寫法二
var m = 1;
export {m};
// 寫法三
var n = 1;
export {n as m};
複製代碼
一樣的,function
和class
的輸出,也必須遵照這樣的寫法。
// 報錯
function f() {}
export f;
// 正確
export function f() {};
// 正確
function f() {}
export {f};
複製代碼
export
語句輸出的接口,與其對應的值是動態綁定關係,即經過該接口,能夠取到模塊內部實時的值。可是不建議這樣作。
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
複製代碼
上面代碼輸出變量foo
,值爲bar
,500 毫秒以後變成baz
。 export
命令能夠出如今模塊的任何位置,只要處於模塊頂層就能夠。若是處於塊級做用域內,就會報錯,下面的import
命令也是如此 2. import命令 使用export
命令定義了模塊的對外接口之後,其餘 JS 文件就能夠經過import
命令加載這個模塊。
// main.js
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
複製代碼
上面代碼的import
命令,用於加載profile.js
文件,並從中輸入變量。import
命令接受一對大括號,裏面指定要從其餘模塊導入的變量名。大括號裏面的變量名,必須與被導入模塊(profile.js
)對外接口的名稱相同。 若是想爲輸入的變量從新取一個名字,import
命令要使用as
關鍵字,將輸入的變量重命名。
import { lastName as surname } from './profile';
複製代碼
import
後面的from
指定模塊文件的位置,能夠是相對路徑,也能夠是絕對路徑,.js
後綴能夠省略。 注意,import
命令具備提高效果,會提高到整個模塊的頭部,首先執行。
foo();
import { foo } from 'my_module';
//import的執行早於foo的調用。這種行爲的本質是,import命令是編譯階段執行的,在代碼運行以前。
複製代碼
因爲import
是靜態執行,因此不能使用表達式和變量,這些只有在運行時才能獲得結果的語法結構。
// 報錯
import { 'f' + 'oo' } from 'my_module';
// 報錯
let module = 'my_module';
import { foo } from module;
// 報錯
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
複製代碼
import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同於
import { foo, bar } from 'my_module';
複製代碼
除了指定加載某個輸出值,還可使用總體加載,即用星號(*
)指定一個對象,全部輸出值都加載在這個對象上面。 注意,模塊總體加載所在的那個對象,不容許運行時改變。下面的寫法都是不容許的。
import * as circle from './circle';
// 下面兩行都是不容許的
circle.foo = 'hello';
circle.area = function () {};
複製代碼
import
命令的時候,用戶須要知道所要加載的變量名或函數名,不然沒法加載。 爲了給用戶提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到export default
命令,爲模塊指定默認輸出。// export-default.js
export default function () {
console.log('foo');
}
複製代碼
其餘模塊加載該模塊時,import
命令能夠爲該匿名函數指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
複製代碼
須要注意的是,這時import
命令後面,不使用大括號。 export default
命令用在非匿名函數前,也是能夠的。
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者寫成
function foo() {
console.log('foo');
}
export default foo;
複製代碼
上面代碼中,foo
函數的函數名foo
,在模塊外部是無效的。加載的時候,視同匿名函數加載。 比較一下默認輸出和正常輸出。
// 第一組
export default function crc32() { // 輸出
// ...
}
import crc32 from 'crc32'; // 輸入
// 第二組
export function crc32() { // 輸出
// ...
};
import {crc32} from 'crc32'; // 輸入
複製代碼
上面代碼的兩組寫法,第一組是使用export default
時,對應的import
語句不須要使用大括號;第二組是不使用export default
時,對應的import
語句須要使用大括號。 export default
命令用於指定模塊的默認輸出。顯然,一個模塊只能有一個默認輸出,所以export default
命令只能使用一次。因此,import命令後面纔不用加大括號,由於只可能惟一對應export default
命令。 本質上,export default
就是輸出一個叫作default
的變量或方法,而後系統容許你爲它取任意名字。因此,下面的寫法是有效的。
// modules.js
function add(x, y) {
return x * y;
}
export {add as default};
// 等同於
// export default add;
// app.js
import { default as foo } from 'modules';
// 等同於
// import foo from 'modules';
複製代碼
正是由於export default
命令其實只是輸出一個叫作default
的變量,因此它後面不能跟變量聲明語句。
// 正確
export var a = 1;
// 正確
var a = 1;
export default a;
// 錯誤
export default var a = 1;
複製代碼
上面代碼中,export default a
的含義是將變量a
的值賦給變量default
。因此,最後一種寫法會報錯。 一樣地,由於export default
命令的本質是將後面的值,賦給default
變量,因此能夠直接將一個值寫在export default
以後。
// 正確
export default 42;
// 報錯
export 42;
複製代碼
import
語句能夠與export
語句寫在一塊兒。export { foo, bar } from 'my_module';
// 等同於
import { foo, bar } from 'my_module';
export { foo, bar };
複製代碼
模塊的接口更名和總體輸出,也能夠採用這種寫法。
// 接口更名
export { foo as myFoo } from 'my_module';
// 總體輸出
export * from 'my_module';
複製代碼