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