《ES6標準入門——阮一峰》總結(jié)

這是筆者讀完阮一峰的《ES6標準入門》的總結(jié),可稱為《ES6標準入門的入門》,總結(jié)的知識點都比較通俗易懂,可為想大概了解ES6但沒有時間閱讀全書的人做一個參考。

1.let 和 const

暫時性死區(qū) (Temporal Dead Zone)

letconst命令聲明的變量無變量提升,且都會被鎖定在聲明的代碼塊中,在letconst命令執(zhí)行前,使用該變量都將報錯,這一部分稱為“暫時性死區(qū)”。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

let tmptmp變量綁定至{}代碼塊之內(nèi),外部的tmp聲明無效,tmp = 'abc'就處在死區(qū),所以報錯。同理在以前沒有letconst命令的時候,typeof是一個安全的運算符,即使變量沒有被聲明,也會正常返回undefined,但如果typeof處在死區(qū)中,處理了在后文被letconst的變量,將會報錯。

typeof undeclared_variable; // undefined 未被let和const聲明反而沒事
if (true) {
  // TDZ開始 (Temporal Dead Zone: 暫時性死區(qū))
  typeof tmp // ReferenceError
  let tmp; // TDZ結(jié)束
  console.log(tmp); // undefined
}
頂層對象

varfunction的全局聲明會自動綁定到windowglobal對象,這是ES5全局變量的一個缺陷,letconst不會。

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)時分為匹配模式和被賦值的變量,若相同可簡寫,注意區(qū)分
// 被解構(gòu)的對象的key,和后邊被賦值的變量同名,可以簡寫。
let { matchValue } = { matchValue: 123 };
console.log(matchValue); //123

等價于

let { matchValue: matchValue } = { matchValue: 123 }
console.log(matchValue); //123

通過代碼的高亮可以看出相互之間的對應(yīng)關(guān)系。所以

let { matchValue: value } = { matchValue: 123 }
console.log(matchValue); //報錯,未定義,這個只是匹配模式,不會被賦值
console.log(value); //123
函數(shù)參數(shù)

首先解構(gòu)賦值允許指定默認值,這為函數(shù)參數(shù)設(shè)置默認值提供基礎(chǔ)。

// 數(shù)組解構(gòu)賦值的默認值
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x = 'a', y = 'b'] = ['aa', undefined]; // x='aa', y='b'

// 對象解構(gòu)賦值的默認值
let {x, y = 5} = {x: 1};
x // 1
y // 5

這里只討論一下參數(shù)為Object類型時,該如何設(shè)置默認值,比如一些options的設(shè)置,通過設(shè)置默認值,可有效避免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的默認參數(shù)表示,如果沒有傳入options,則用一個沒有鍵值對的對象{}作為默認值,但也正是因此,傳入的options沒有options.typeoptions.dataType這兩個屬性,則賦予默認值type = 'GET', dataType = 'json',這是針對鍵值對某一個key設(shè)默認值。

ajax2的默認參數(shù)表示,如果沒有傳入options對象,則用一個{ type = 'GET', dataType = 'json' }這樣的options對象作為默認值,這是針對這一整個options設(shè)默認值。弊端就是如果開發(fā)者在使用時這樣寫

ajax2(url, {type = 'POST'})

那么dataType參數(shù)將要丟失,因為{type = 'POST'}代替了默認參數(shù){ type = 'GET', dataType = 'json' },所以一般通過形如ajax1的方式定義默認參數(shù)。

ajax3的默認參數(shù)有一個問題,就是當沒有傳入options的時候,相當于從undefined中取值type,dataType來解構(gòu),所以會報錯,所以ajax1會通過= {}的方式,把不傳入options的情況過濾掉。

3.各種數(shù)據(jù)結(jié)構(gòu)的擴展

字符串

``表示模板字符串,就不多介紹了,功能強大好用。
--------------------
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只能兩字節(jié)兩字節(jié)的分開返回
s.charCodeAt(1) // 57271

下面看一下ES6的codePointAtfor...of

let s = '??a';
s.codePointAt(0) // 134071 可以識別一整個字
s.codePointAt(1) // 57271 第三,四字節(jié)會被返回
s.codePointAt(2) // 97 字符串長度仍有問題
// 使用for...of循環(huán)正確處理
for (let ch of s) {
  console.log(ch.codePointAt(0).toString(16));
}
// 20bb7 134071是10進制,20bb7為16進制表示
// 61 字符串長度也沒問題,循環(huán)執(zhí)行了2次

--------------------
還引入了includes,startWithendWith,padStart,padEnd等方法,具體使用方法可以通過API手冊了解一下。

Number

parseInt等全局方法掛在到Number上,如Number.parseInt,Number.parseFloat等,增加了一些高階計算函數(shù)。

函數(shù)

箭頭函數(shù),this的指向在函數(shù)生成時固定,說白了就是this指向與外部一致。
--------------------
函數(shù)參數(shù)設(shè)置默認值,前文已經(jīng)說明。補充一點,設(shè)置某參數(shù)必須可以:

function throwNeedThisParamException(param) {
  throw new Error(`Missing parameter: ${param}`);
}
function demo (x = throwNeedThisParamException('x')) {

}

參數(shù)的默認值是在取不到值的情況下才會執(zhí)行,所以正常情況不會拋出這個錯誤。
--------------------
參數(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),可以看見有一個Symbol類型的字段Symbol.iterator,這是它的迭代器,使其可以向數(shù)組一樣遍歷。但如果展開看其__proto__,值為Object。而使用rest形式的參數(shù),可以直接將參數(shù)轉(zhuǎn)為數(shù)組。注意rest形式的參數(shù)只能用作最后一個參數(shù)。
--------------------
函數(shù)的length屬性返回函數(shù)參數(shù)的個數(shù),name屬性返回聲明的函數(shù)名稱,ES6的變量式聲明返回變量名。

function f1 () {}
f1.name // f1
const f2 = function (x,y) {}
f2.name // f2
f2.length // 2

--------------------
雙冒號運算符,代替bind,callapply綁定this對象指向。foo::bar(arguments)相當于bar.apply(foo, arguments)
--------------------
尾調(diào)用,說白了就是最后返回值為執(zhí)行另一個函數(shù)return anotherFunction(),而return anotherFunction() + 1不屬于尾調(diào)用,因為在執(zhí)行完anotherFunction后還需要+1。尾調(diào)用的優(yōu)勢就是在return后,可以釋放當前函數(shù)執(zhí)行所需要的一切資源空間。對比如下兩個例子,是做斐波那契數(shù)列求值的:

function Fibonacci (n) {
  if ( n <= 1 ) {return 1};

  return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 89
Fibonacci(100) // 堆棧溢出
Fibonacci(500) // 堆棧溢出

這是最簡單的寫法,清晰明了,第n項就是前兩項的和。但是,為了計算加號兩邊的值,必須要保存函數(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ù)所需要的資源,所以不會堆棧溢出,只是在邏輯上不好理解。這種寫法Fibonacci2 (n - 1, ac2, ac1 + ac2)可以看成一個從前到后推導過程,n相當于一個計數(shù)器,每次值的增長是通過兩個數(shù)求和ac1 + ac2作為第二個數(shù),ac2作為第一個數(shù)。

數(shù)組

擴展運算符...,與上文的rest參數(shù)是相反的用法,rest參數(shù)是把一個個的參數(shù)總和到數(shù)組rest參數(shù)中,而擴展運算符是把數(shù)組中的元素一個個提取出來。
擴展運算符可以用來方便的復(fù)制一個數(shù)組。

let arr = [1,2,3]
console.log(...arr)  // 等價于console.log(1,2,3)
let arr2 = [...arr] // 等價于let arr2 = [1,2,3],新建一個數(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(),因為new Array()的參數(shù)與行為不統(tǒng)一,當傳入一個參數(shù)且為數(shù)字時,表示數(shù)組長度,Array.of不會有這個問題,會通過參數(shù)創(chuàng)建數(shù)組。
Array還新增了一些工具方法,如find,findIndexincludes等可以參考其他API手冊。

對象

Object.assign是合并對象,把多個對象合并到第一個對象上。
Object.create是以某原型,生成一個新對象。可選第二個參數(shù),為屬性描述符,使用方式參見下方代碼。
Object.getPrototypeOfObject.setPrototypeOf是獲取和設(shè)置對象的原型屬性__proto__,不應(yīng)顯式使用__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允許字面量定義對象時,用表達式作為屬性名,把表達式放在方括號內(nèi)。

const propKey = 'foo';

const obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};
obj // { foo: true, abc: 123 }

--------------------
Object.is優(yōu)化了===運算符,處理了===的兩個問題。

NaN === NaN // false
Object.is(NaN, NaN) // true
--------------
+0 === -0 // true 
Object.is(+0, -0) // false

4.Symbol

Symbol為不會重復(fù)的值,第七種基本數(shù)據(jù)類型,類似字符串,可以作為對象的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可以去確保生成的值不同,但有時需要保存下來以便再次使用,類似于單例,如果存在就不會重新創(chuàng)建。這個時候就需要使用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會返回已登記的Symbolkey,一定是登記過的才會返回。接上例:

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 個靜態(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)
    通過ProxyReflect可以實現(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ù)為thencatch后需要執(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('出錯了', error);
});

7. Iterator 和 for...of循環(huán)

Iterator被掛載在對象的Symbol.iterator屬性下,Symbol.iterator不是一個Iterator,而是會返回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會生成一個Iterator,每次iterator.next()返回yield的產(chǎn)出值,且中斷程序執(zhí)行。yield*表示產(chǎn)出的值是另外一個generator的結(jié)果。代碼如下:

function* demo(){
  console.log(`${yield 1}`);
  console.log(`${yield 2}`);
  yield* demo2(); //返回另一個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} 

解釋一下運行結(jié)果:第一次ite.next()時,程序執(zhí)行到yield 1被終止,故沒有打印日志,再次執(zhí)行ite.next()時,代碼繼續(xù),開始執(zhí)行console.log(`${yield 1}`);,但輸出不是1而是undefiend,因為ite.next()的參數(shù)值會被當做上次yield語句的執(zhí)行結(jié)果,所以下面的ite.next(123456789)會輸出數(shù)字123456789。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 聽歌的時候,我都會去關(guān)注一下歌詞,這里收集了一些自己特別喜歡并且覺得寫入心里去的。缺少了歌詞,或許再好的曲調(diào)也只會...
    挑兮達兮閱讀 734評論 0 2
  • 年少時我們有足夠多的理由去認認真真地喜歡另一個人,而長大后我們有同樣多的理由去認認真真地辜負另一個人。所以最初...
    Silvia15閱讀 783評論 3 2

友情鏈接更多精彩內(nèi)容