這是筆者讀完阮一峰的《ES6標(biāo)準(zhǔn)入門》的總結(jié),可稱為《ES6標(biāo)準(zhǔn)入門的入門》,總結(jié)的知識點(diǎn)都比較通俗易懂,可為想大概了解ES6但沒有時(shí)間閱讀全書的人做一個(gè)參考。
1.let 和 const
暫時(shí)性死區(qū) (Temporal Dead Zone)
let
和const
命令聲明的變量無變量提升,且都會(huì)被鎖定在聲明的代碼塊中,在let
和const
命令執(zhí)行前,使用該變量都將報(bào)錯(cuò),這一部分稱為“暫時(shí)性死區(qū)”。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
let tmp
將tmp
變量綁定至{}
代碼塊之內(nèi),外部的tmp
聲明無效,tmp = 'abc'
就處在死區(qū),所以報(bào)錯(cuò)。同理在以前沒有let
和const
命令的時(shí)候,typeof
是一個(gè)安全的運(yùn)算符,即使變量沒有被聲明,也會(huì)正常返回undefined
,但如果typeof處在死區(qū)中,處理了在后文被let
和const
的變量,將會(huì)報(bào)錯(cuò)。
typeof undeclared_variable; // undefined 未被let和const聲明反而沒事
if (true) {
// TDZ開始 (Temporal Dead Zone: 暫時(shí)性死區(qū))
typeof tmp // ReferenceError
let tmp; // TDZ結(jié)束
console.log(tmp); // undefined
}
頂層對象
var
和function
的全局聲明會(huì)自動(dòng)綁定到window
或global
對象,這是ES5全局變量的一個(gè)缺陷,let
和const
不會(huì)。
var a = 1;
// 如果在 Node 的 REPL 環(huán)境,可以寫成 global.a
// 或者采用通用方法,寫成 this.a
window.a // 1
let b = 1;
window.b // undefined
const命令
const
聲明的變量只是引用無法修改,對象的內(nèi)部結(jié)構(gòu)可以改變,使用Object.freeze()
可以徹底鎖定某對象,需遞歸鎖定多層級對象。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
2.變量的解構(gòu)賦值
解構(gòu)時(shí)分為匹配模式和被賦值的變量,若相同可簡寫,注意區(qū)分
// 被解構(gòu)的對象的key,和后邊被賦值的變量同名,可以簡寫。
let { matchValue } = { matchValue: 123 };
console.log(matchValue); //123
等價(jià)于
let { matchValue: matchValue } = { matchValue: 123 }
console.log(matchValue); //123
通過代碼的高亮可以看出相互之間的對應(yīng)關(guān)系。所以
let { matchValue: value } = { matchValue: 123 }
console.log(matchValue); //報(bào)錯(cuò),未定義,這個(gè)只是匹配模式,不會(huì)被賦值
console.log(value); //123
函數(shù)參數(shù)
首先解構(gòu)賦值允許指定默認(rèn)值,這為函數(shù)參數(shù)設(shè)置默認(rèn)值提供基礎(chǔ)。
// 數(shù)組解構(gòu)賦值的默認(rèn)值
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x = 'a', y = 'b'] = ['aa', undefined]; // x='aa', y='b'
// 對象解構(gòu)賦值的默認(rèn)值
let {x, y = 5} = {x: 1};
x // 1
y // 5
這里只討論一下參數(shù)為Object
類型時(shí),該如何設(shè)置默認(rèn)值,比如一些options
的設(shè)置,通過設(shè)置默認(rèn)值,可有效避免var foo = options.foo || 'default foo';
。有三種形式,注意這三種的區(qū)別:
const ajax1 = function (url, { type = 'GET', dataType = 'json'} = {}) {
// TODO
}
const ajax2 = function (url, {} = { type = 'GET', dataType = 'json' }) {
// TODO
}
const ajax3 = function (url, { type = 'GET', dataType = 'json'} ) {
// TODO
}
ajax1
的默認(rèn)參數(shù)表示,如果沒有傳入options
,則用一個(gè)沒有鍵值對的對象{}
作為默認(rèn)值,但也正是因此,傳入的options
沒有options.type
和 options.dataType
這兩個(gè)屬性,則賦予默認(rèn)值type = 'GET', dataType = 'json'
,這是針對鍵值對某一個(gè)key
設(shè)默認(rèn)值。
ajax2
的默認(rèn)參數(shù)表示,如果沒有傳入options
對象,則用一個(gè){ type = 'GET', dataType = 'json' }
這樣的options
對象作為默認(rèn)值,這是針對這一整個(gè)options
設(shè)默認(rèn)值。弊端就是如果開發(fā)者在使用時(shí)這樣寫
ajax2(url, {type = 'POST'})
那么dataType
參數(shù)將要丟失,因?yàn)?code>{type = 'POST'}代替了默認(rèn)參數(shù){ type = 'GET', dataType = 'json' }
,所以一般通過形如ajax1
的方式定義默認(rèn)參數(shù)。
ajax3
的默認(rèn)參數(shù)有一個(gè)問題,就是當(dāng)沒有傳入options
的時(shí)候,相當(dāng)于從undefined
中取值type
,dataType
來解構(gòu),所以會(huì)報(bào)錯(cuò),所以ajax1
會(huì)通過= {}
的方式,把不傳入options
的情況過濾掉。
3.各種數(shù)據(jù)結(jié)構(gòu)的擴(kuò)展
字符串
``
表示模板字符串,就不多介紹了,功能強(qiáng)大好用。
--------------------
codePointAt
可作為charCodeAt
的替代品,必要時(shí)使用for...of
遍歷字符串,他們都是為了處理32 位的 UTF-16 字符。
var s = "??a";
s.length // 3 無法正確識別字符串長度,會(huì)把‘??’識別為2個(gè)字符
s.charAt(0) // '' charAt無法處理這個(gè)字
s.charAt(1) // ''
s.charCodeAt(0) // 55362 charCodeAt只能兩字節(jié)兩字節(jié)的分開返回
s.charCodeAt(1) // 57271
下面看一下ES6的codePointAt
和for...of
let s = '??a';
s.codePointAt(0) // 134071 可以識別一整個(gè)字
s.codePointAt(1) // 57271 第三,四字節(jié)會(huì)被返回
s.codePointAt(2) // 97 字符串長度仍有問題
// 使用for...of循環(huán)正確處理
for (let ch of s) {
console.log(ch.codePointAt(0).toString(16));
}
// 20bb7 134071是10進(jìn)制,20bb7為16進(jìn)制表示
// 61 字符串長度也沒問題,循環(huán)執(zhí)行了2次
--------------------
還引入了includes
,startWith
,endWith
,padStart
,padEnd
等方法,具體使用方法可以通過API手冊了解一下。
Number
parseInt
等全局方法掛在到Number
上,如Number.parseInt
,Number.parseFloat
等,增加了一些高階計(jì)算函數(shù)。
函數(shù)
箭頭函數(shù),this
的指向在函數(shù)生成時(shí)固定,說白了就是this
指向與外部一致。
--------------------
函數(shù)參數(shù)設(shè)置默認(rèn)值,前文已經(jīng)說明。補(bǔ)充一點(diǎn),設(shè)置某參數(shù)必須可以:
function throwNeedThisParamException(param) {
throw new Error(`Missing parameter: ${param}`);
}
function demo (x = throwNeedThisParamException('x')) {
}
參數(shù)的默認(rèn)值是在取不到值的情況下才會(huì)執(zhí)行,所以正常情況不會(huì)拋出這個(gè)錯(cuò)誤。
--------------------
參數(shù)的rest
形式,例子如下:
function demo (...values) {
console.log(values)
console.log('-----------------------')
console.log(arguments)
}
demo(1,2,3,4)
//[1,2,3,4]
-----------------------
//[1, 2, 3, 4, callee: (...), Symbol(Symbol.iterator): ?]
內(nèi)置的arguments
為類數(shù)組結(jié)構(gòu),可以看見有一個(gè)Symbol
類型的字段Symbol.iterator
,這是它的迭代器,使其可以向數(shù)組一樣遍歷。但如果展開看其__proto__
,值為Object
。而使用rest
形式的參數(shù),可以直接將參數(shù)轉(zhuǎn)為數(shù)組。注意rest
形式的參數(shù)只能用作最后一個(gè)參數(shù)。
--------------------
函數(shù)的length
屬性返回函數(shù)參數(shù)的個(gè)數(shù),name
屬性返回聲明的函數(shù)名稱,ES6的變量式聲明返回變量名。
function f1 () {}
f1.name // f1
const f2 = function (x,y) {}
f2.name // f2
f2.length // 2
--------------------
雙冒號運(yùn)算符,代替bind
,call
,apply
綁定this
對象指向。foo::bar(arguments)
相當(dāng)于bar.apply(foo, arguments)
--------------------
尾調(diào)用,說白了就是最后返回值為執(zhí)行另一個(gè)函數(shù)return anotherFunction()
,而return anotherFunction() + 1
不屬于尾調(diào)用,因?yàn)樵趫?zhí)行完anotherFunction
后還需要+1
。尾調(diào)用的優(yōu)勢就是在return
后,可以釋放當(dāng)前函數(shù)執(zhí)行所需要的一切資源空間。對比如下兩個(gè)例子,是做斐波那契數(shù)列求值的:
function Fibonacci (n) {
if ( n <= 1 ) {return 1};
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 89
Fibonacci(100) // 堆棧溢出
Fibonacci(500) // 堆棧溢出
這是最簡單的寫法,清晰明了,第n項(xiàng)就是前兩項(xiàng)的和。但是,為了計(jì)算加號兩邊的值,必須要保存函數(shù)執(zhí)行的全部資源,遞歸后造成堆棧溢出。這不屬于尾調(diào)用。
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};
return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity
這是優(yōu)化過的遞歸調(diào)用,return
之后無需保存函數(shù)所需要的資源,所以不會(huì)堆棧溢出,只是在邏輯上不好理解。這種寫法Fibonacci2 (n - 1, ac2, ac1 + ac2)
可以看成一個(gè)從前到后推導(dǎo)過程,n
相當(dāng)于一個(gè)計(jì)數(shù)器,每次值的增長是通過兩個(gè)數(shù)求和ac1 + ac2
作為第二個(gè)數(shù),ac2
作為第一個(gè)數(shù)。
數(shù)組
擴(kuò)展運(yùn)算符...
,與上文的rest參數(shù)
是相反的用法,rest參數(shù)
是把一個(gè)個(gè)的參數(shù)總和到數(shù)組rest參數(shù)
中,而擴(kuò)展運(yùn)算符是把數(shù)組中的元素一個(gè)個(gè)提取出來。
擴(kuò)展運(yùn)算符可以用來方便的復(fù)制一個(gè)數(shù)組。
let arr = [1,2,3]
console.log(...arr) // 等價(jià)于console.log(1,2,3)
let arr2 = [...arr] // 等價(jià)于let arr2 = [1,2,3],新建一個(gè)數(shù)組
arr.push(4)
console.log(arr2) // [1,2,3]
--------------------
數(shù)組可以通過Array.from
,Array.of
生成,可以通過keys()
,values()
,entries()
遍歷。
Array.from
可以從具有iterator
的數(shù)據(jù)結(jié)構(gòu)生成數(shù)組,比如arguments
對象,document.querySelectorAll()
獲得的DOM對象,這些都是類數(shù)組,或者Map
,Set
等新增的數(shù)據(jù)結(jié)構(gòu)。
Array.of
可以代替new Array()
,因?yàn)?code>new Array()的參數(shù)與行為不統(tǒng)一,當(dāng)傳入一個(gè)參數(shù)且為數(shù)字時(shí),表示數(shù)組長度,Array.of
不會(huì)有這個(gè)問題,會(huì)通過參數(shù)創(chuàng)建數(shù)組。
Array還新增了一些工具方法,如find
,findIndex
,includes
等可以參考其他API手冊。
對象
Object.assign
是合并對象,把多個(gè)對象合并到第一個(gè)對象上。
Object.create
是以某原型,生成一個(gè)新對象。可選第二個(gè)參數(shù),為屬性描述符,使用方式參見下方代碼。
Object.getPrototypeOf
,Object.setPrototypeOf
是獲取和設(shè)置對象的原型屬性__proto__
,不應(yīng)顯式使用__proto__
這個(gè)屬性。
Object.getOwnPropertyDescriptors
是獲取對象的屬性信息,包括value
,writable
,enumerable
,configurable
。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
-------------------
Object.setPrototypeOf(target, { myProto: 'PROTO'})
Object.getPrototypeOf(target) //{ myProto: 'PROTO', __proto__: Object}
let newObj = Object.create(Object.getPrototypeOf(target))
newObj // 無顯式屬性{ __proto__:{ myProto: 'PROTO', __proto__: Object} }
-------------------
const descriptors = Object.getOwnPropertyDescriptors(target)
console.log(descriptors)
// {
// a: {value: 1, writable: true, enumerable: true, configurable: true},
// b: {value: 2, writable: true, enumerable: true, configurable: true},
// c: {value: 3, writable: true, enumerable: true, configurable: true}
// }
newObj = Object.create(Object.getPrototypeOf(target), descriptors)
newObj // { a:1, b:2, c:3, __proto__:{ myProto: 'PROTO', __proto__: Object} }
--------------------
ES6允許字面量定義對象時(shí),用表達(dá)式作為屬性名,把表達(dá)式放在方括號內(nèi)。
const propKey = 'foo';
const obj = {
[propKey]: true,
['a' + 'bc']: 123
};
obj // { foo: true, abc: 123 }
--------------------
Object.is
優(yōu)化了===
運(yùn)算符,處理了===
的兩個(gè)問題。
NaN === NaN // false
Object.is(NaN, NaN) // true
--------------
+0 === -0 // true
Object.is(+0, -0) // false
4.Symbol
Symbol
為不會(huì)重復(fù)的值,第七種基本數(shù)據(jù)類型,類似字符串,可以作為對象的key
,但不會(huì)被for...of
,for...in
,Object.getOwnPropertyNames()
,Object.keys()
返回,如需要遍歷,需使用Object.getOwnPropertySymbols()
,或者Reflect.ownKeys()
返回全部key
。
let foo = Symbol('foo');
const obj = { [foo]: 'foobar' }
for (let i in obj) {
console.log(i); // 無輸出
}
Object.getOwnPropertyNames(obj)
// []
Object.getOwnPropertySymbols(obj)
// [Symbol(foo)]
Reflect.ownKeys(obj)
// [Symbol(foo)]
Symbol.for() 和 Symbol.keyFor()
Symbol
可以去確保生成的值不同,但有時(shí)需要保存下來以便再次使用,類似于單例,如果存在就不會(huì)重新創(chuàng)建。這個(gè)時(shí)候就需要使用Symbol.for()
。
let s1 = Symbol('foo');
let s2 = Symbol.for('foo');
let s3 = Symbol.for('foo');
s1 === s2 // false
s2 === s3 // true
從上例可以看出,Symbol.for
類似于將這個(gè)Symbol
登記,所以s1
這個(gè)未登記的Symbol
不會(huì)等于其他Symbol
。
Symbol.keyFor
會(huì)返回已登記的Symbol
的key
,一定是登記過的才會(huì)返回。接上例:
Symbol.keyFor(s1) // undefiend
Symbol.keyFor(s2) // "foo"
5.Proxy和Reflect
Proxy
代理對象的各種內(nèi)置方法,get
set
construct
等,類似于攔截器。
Reflect
則作為Object
的替代者,Object
上的一些靜態(tài)方法被移植到了Reflect
上。
Reflect
對象一共有 13 個(gè)靜態(tài)方法。
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
通過Proxy
和Reflect
可以實(shí)現(xiàn)觀察者模式,說白了就是:監(jiān)聽set
方法,執(zhí)行相應(yīng)操作。
const person = { name: 'Li', age: 18}
const personObserved = observe(person)
function observe(obj) {
return new Proxy(obj, {
set: function (target, key, value, receiver) {
console.log(`setting ${key} to ${value}!`);
return Reflect.set(target, key, value, receiver);
}
})
}
personObserved.name = 'zhang'
// setting name to zhang!
6.Promise
Promise
用來處理異步操作,是構(gòu)造函數(shù),參數(shù)為then
和catch
后需要執(zhí)行的方法。下面是使用Promise
封裝的ajax
:
const getJSON = function(url) {
const promise = new Promise((resolve, reject) => {
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出錯(cuò)了', error);
});
7. Iterator 和 for...of循環(huán)
Iterator
被掛載在對象的Symbol.iterator
屬性下,Symbol.iterator
不是一個(gè)Iterator
,而是會(huì)返回Iterator
的函數(shù)。
const arr = [1,2,3,4,5]
let iterator = arr[Symbol.iterator]();
iterator // Array Iterator {}
iterator.next() // {value: 1, done: false}
......
iterator.next() // {value: 5, done: false}
iterator.next() // {value: undefined, done: true}
8. Generator 和 yield
Generator
會(huì)生成一個(gè)Iterator
,每次iterator.next()
返回yield
的產(chǎn)出值,且中斷程序執(zhí)行。yield*
表示產(chǎn)出的值是另外一個(gè)generator的結(jié)果。代碼如下:
function* demo(){
console.log(`${yield 1}`);
console.log(`${yield 2}`);
yield* demo2(); //返回另一個(gè)generator的結(jié)果
}
function* demo2(){
yield 3;
}
let ite = demo();
ite.next() // 返回值:{value: 1, done: false}
ite.next() // console:undefined, 返回值:{value: 2, done: false}
ite.next(123456789) // console: 123456789, 返回值:{value: 3, done: false}
解釋一下運(yùn)行結(jié)果:第一次ite.next()
時(shí),程序執(zhí)行到yield 1
被終止,故沒有打印日志,再次執(zhí)行ite.next()時(shí),代碼繼續(xù),開始執(zhí)行console.log(`${yield 1}`);
,但輸出不是1
而是undefiend
,因?yàn)?code>ite.next()的參數(shù)值會(huì)被當(dāng)做上次yield
語句的執(zhí)行結(jié)果,所以下面的ite.next(123456789)
會(huì)輸出數(shù)字123456789
。