<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ES6 你可能不知道的事 - 進(jìn)階篇

          共 9183字,需瀏覽 19分鐘

           ·

          2020-07-28 16:19


          前言

          這篇文章主要會針對上篇未涉及到的進(jìn)階特性展開;而與前一篇文章相同,本文主要介紹這些特性的一些容易忽略的部分,希望能對大家正確認(rèn)識和使用 ES6 有幫助。

          還是那句話,時間和能力有限,針對文章中的問題或不同意見,歡迎隨時拍磚、指正!

          正文

          Module

          模塊化是個進(jìn)行了很久的話題,發(fā)展歷程中出現(xiàn)過很多模式,例如 AMD, CommonJS 等等。

          Module 是 ES6 的新特性,是語言層面對模塊化的支持。

          與之前模塊加載機(jī)制不同,Module 是動態(tài)的加載,導(dǎo)入的是變量的 只讀引用 ,而不是拷貝

          // 1. export default 可以做默認(rèn)導(dǎo)出// a.jsexport default 5; ? ? ?// 默認(rèn)導(dǎo)出// b.jsimport b, {a} from './a.js'; ? ?// 默認(rèn)導(dǎo)入,不需要加花括號// 2. 動態(tài)的加載機(jī)制// a.jsexport let a = 10;
          export let b = 10;
          export function add() {
          ?a = 15;
          ?b = 20; ?return a+b;
          };// b.jsimport {a, b, add} from './a.js';
          a+b; ? ?// 20add(); ?// 35a+b; ? ?// 35

          Symbol

          symbol 是 ES6 的一個新特性,他有如下特點:

          • symbol 是一個 “新” 的 基礎(chǔ)數(shù)據(jù)類型 ;從 ES6 起,JavaScript 的 基礎(chǔ)數(shù)據(jù)類型 變?yōu)?6 個:string, number, boolean, null, undefined, symbol

          • symbol 可以用作 Object 的 key

          • symbol 存在全局作用域,利用 Symbol.for(key) 方法,可以創(chuàng)建(全局作用域無指定鍵)或獲取全局作用域內(nèi)的 symbol ;利用 Symbol.keyFor(sym) 方法,可以獲取指定 symbol 的鍵

          • JavaScript 內(nèi)部使用了很多內(nèi)置 symbol ,作為特殊的鍵,來實現(xiàn)一些內(nèi)部功能;例如 Symbol.iterator 用于標(biāo)示對象的迭代器

          “新” 僅僅是針對前端開發(fā)人員來說的,其實 Symbol 概念本身已經(jīng)在 JavaScript 語言內(nèi)部長時間使用了

          // 1. "Symbol(desc)" 方法用于創(chuàng)建一個新的 symbol,參數(shù) "desc" 僅用做 symbol 的描述,并不用于唯一標(biāo)示 symbolSymbol('abc') === Symbol('abc'); ? ?// false,'abc'僅作為兩個 symbol 的描述信息// 2. "Symbol.for(key)" 方法,參數(shù) "key" 是用于在全局作用域中標(biāo)示 symbol 的唯一鍵,同時也作為該 symbol 的描述信息Symbol.for('abc') === Symbol.for('abc'); ? ?// true,左側(cè)為創(chuàng)建,右側(cè)為獲取Symbol.for('abc') === Symbol('abc'); ? ? ? ?// false// 3. symbol 無法被 for...in 遍歷到 (不可枚舉),可以利用 Object.getOwnPropertySymbols 獲取const obj = {
          ?[Symbol('abc')]: 'abc', ?'abc': 'abc'};for(var i in obj){ ?// abc
          ?console.log(i);
          }Object.getOwnPropertySymbols(obj); ? ?// [Symbol(abc)]

          Iterator + For..Of

          ES6 中除了新特性外,還有一個新的規(guī)范,那就是關(guān)于迭代的規(guī)范,他包括兩部分分別是 “可迭代規(guī)范(iterable protocol)” 和 “迭代器規(guī)范(iterator protocol)”。任何實現(xiàn)了前者的對象,都可以進(jìn)行 for…of 循環(huán)。

          String, Array, Map, Set等是原生可迭代對象,因為他們都在原型(prototype)對象中實現(xiàn)了 Symbol.iterator 鍵對應(yīng)的方法

          for…of 是對象迭代器的遍歷,而 for…in 是對象中 可枚舉 值的遍歷

          下面用代碼來解釋一下兩個規(guī)范:

          // 1. 迭代器規(guī)范const iter = {
          ?counter: 0,
          ?next(){ ? ?// 迭代器是實現(xiàn)了 "next()" 函數(shù)的對象
          ? ?if(++this.counter < 10){ ? ? ?return { ? ?// 返回一個含有兩個鍵值對的對象,Object {done => boolean, value => any}
          ? ? ? ?done: false,
          ? ? ? ?value: this.counter
          ? ? ?}
          ? ?}else{ ? ? ?this.counter = 0; ? ? ?return { ? ?// done = true 時,value非必須
          ? ? ? ?done: true
          ? ? ?}
          ? ?}
          ?}
          };// 2. 可迭代規(guī)范,實現(xiàn) "Symbol.iterator => func()" 鍵值對;而 "func()" 返回一個 迭代器對象const iterObj = {};for(var i of iterObj){}; ? ?// TypeError: iterObj is not iterableiterObj[Symbol.iterator] = function() { ?return iter;
          };for(var i of iterObj){ ?console.log(i); ? ?// 1,2,3,4,5,6,7,8,9};

          關(guān)于集合

          原來我們使用集合,多數(shù)情況下會直接用 Object 代替,ES6新增了兩個特性,MapSet,他們是對 JavaScript 關(guān)于集合概念的補(bǔ)充。

          Map

          剛剛看到這個概念的同學(xué)會有幾個常見的疑問,為什么我們需要 Map 這種數(shù)據(jù)結(jié)構(gòu)?直接用 Object 不好么?是不是 Map 可以完全取代 Object 用于數(shù)據(jù)存取?

          MapObject 的區(qū)別

          • MapObject 都可以存取數(shù)據(jù),Map 適用于存儲需要 常需要變化(增減鍵值對)或遍歷 的數(shù)據(jù)集,而 Object 適用于存儲 靜態(tài) (例如配置信息)數(shù)據(jù)集

          • Object 的 key 必須是 StringSymbol 類型的,而 Map 無此限制,可以是任何值

          • Map 可以很方便的取到鍵值對數(shù)量,而 Object 需要用額外途徑

          // 1. Map 的構(gòu)造函數(shù)可以傳入一個 “可迭代的對象(例如數(shù)組)”,其中包含鍵值對數(shù)組const first = new Map([['a', 1], [{'b': 1}, 2]]); ? ?// Map {"a" => 1, Object {b: 1} => 2}// 2. Map 的鍵可以是任何值,甚至是 undefined 或 nullfirst.set(null, 1).set(undefined, 0); ? ?// Map {"a" => 1, Object {b: 1} => 2, null => 1, undefined => 0}

          Set

          Set 作為最簡單的集合,有著如下幾個特點:

          • Set 可以存儲任何類型的值,遍歷順序與 插入順序相同

          • Set 內(nèi)無重復(fù)的值

          // 1. Set 的構(gòu)造函數(shù)可以傳入一個 “可迭代的對象(例如數(shù)組)”,其中包含任意值const first = new Set(['a', 1, {'b': 1}, null]); ? ?// Set {"a", 1, Object {b: 1}, null}// 2. Set 無法插入重復(fù)的值first.add(1); ? ?// Set {"a", 1, Object {b: 1}, null}

          WeakMap + WeakSet

          WeakMapWeakSet 作為一個比較新穎的概念,其主要特點在于弱引用。

          相比于 MapSet 的強(qiáng)引用,弱引用可以令對象在 “適當(dāng)” 情況下正確被 GC 回收,減少內(nèi)存資源浪費。

          但由于不是強(qiáng)引用,所以無法進(jìn)行遍歷或取得值數(shù)量,只能用于值的存取(WeakMap)或是否存在值得判斷(WeakSet)

          在弱引用的情況下,GC 回收時,不會把其視作一個引用;如果沒有其他強(qiáng)引用存在,那這個對象將被回收

          // 1. WeakMap 鍵必須是對象const err = new WeakMap([['a',1]]); ? ?// TypeError: Invalid value used as weak map key// 2. WeakMap/WeakSet 的弱引用const wm = new WeakMap([[{'a':1},1]]); ? ?// Object {'a': 1} 會正常被 GC 回收const ws = new WeakSet(); 
          ws.add({'a':1}); ? ?// Object {'a': 1} 會正常被 GC 回收const obj = {'b': 1};
          ws.add(obj); ? ? ? ?// Object {'b': 1} 不會被正常 GC 回收,因為存在一個強(qiáng)引用obj = undefined; ? ?// Object {'b': 1} 會正常被 GC 回收

          異步編程

          在 ES6 之前,JavaScript 的異步編程都跳不出回調(diào)函數(shù)這個方式。回調(diào)函數(shù)方式使用非常簡單,在簡單異步任務(wù)調(diào)用時候沒有任何問題,但如果出現(xiàn)復(fù)雜的異步任務(wù)場景時,就顯得力不從心了,最主要的問題就是多層回調(diào)函數(shù)的嵌套會導(dǎo)致代碼的橫向發(fā)展,難以維護(hù);ES6 帶來了兩個新特性來解決異步編程的難題。

          // 一個簡單的多層嵌套回調(diào)函數(shù)的例子 (Node.js)const git = require('shell').git;const commitMsg = '...';

          git.add('pattern/for/some/files/*', (err) => { ?if(!err){
          ? ?git.commit(commitMsg, (err) => { ? ? ?if(!err){
          ? ? ? ?git.push(pushOption);
          ? ? ?}else{ ? ? ? ?console.log(err);
          ? ? ?}
          ? ?});
          ?}else{ ? ?console.log(err);
          ?}
          });

          Promise

          Promise 是 ES6 的一個新特性,同為異步編程方式,它主要有如下幾個特點:

          • 本質(zhì)還是回調(diào)函數(shù)

          • 區(qū)分成功和失敗的回調(diào),省去嵌套在內(nèi)層的判斷邏輯

          • 可以很輕松的完成回調(diào)函數(shù)模式到 Promise 模式的轉(zhuǎn)化

          • 代碼由回調(diào)函數(shù)嵌套的橫向擴(kuò)展,變?yōu)殒準(zhǔn)秸{(diào)用的縱向擴(kuò)展,易于理解和維護(hù)

          Promise 雖然優(yōu)勢頗多,但是代碼結(jié)構(gòu)仍與同步代碼區(qū)別較大

          // 上例用 Promise 實現(xiàn)// 假定 git.add, git.commit, git.push 均做了 Promise 封裝,返回一個 Promise 對象const git = require('shell').git;const commitMsg = '...';

          git.add('pattern/for/some/files/*')
          ?.then(() => git.commit(commitMsg))
          ?.then(git.push)
          ?.catch((err) => { ? ?console.log(err);
          ?});

          Generator

          Generator 作為 ES6 的新特性,是一個語言層面的升級。它有以下幾個特點:

          • 可以通過 yield 關(guān)鍵字,終止執(zhí)行并返回(內(nèi)到外)

          • 可以通過 next(val) 方法調(diào)用重新喚醒,繼續(xù)執(zhí)行(外回內(nèi))

          • 運行時(包括掛起態(tài)),共享局部變量

          • Generator 執(zhí)行會返回一個結(jié)果對象,結(jié)果對象本身既是迭代器,同時也是可迭代對象(同時滿足兩個迭代規(guī)范),所以 Generator 可以直接用于 自定義對象迭代器

          由于具備以上特點(第四點除外),Generator 也是 JavaScript 對 協(xié)程(coroutine)的實現(xiàn),協(xié)程可以理解為 “可由開發(fā)人員控制調(diào)度的多線程”

          協(xié)程按照調(diào)度機(jī)制來區(qū)分,可以分為對稱式和非對稱式

          非對稱式:被調(diào)用者(協(xié)程)掛起時,必須將控制權(quán)返還調(diào)用者(協(xié)程)

          對稱式:被調(diào)用者(協(xié)程)掛起時,可將控制權(quán)轉(zhuǎn)給 “任意” 其他協(xié)程

          JavaScript 實現(xiàn)的是 非對稱式協(xié)程(semi-coroutine);非對稱式協(xié)程相比于對稱式協(xié)程,代碼邏輯更清晰,易于理解和維護(hù)

          協(xié)程給 JavaScript 提供了一個新的方式去完成異步編程;由于 Generator 的執(zhí)行會返回一個迭代器,需要手動去遍歷,所以如果要達(dá)到自動執(zhí)行的目的,除了本身語法外,還需要實現(xiàn)一個執(zhí)行器,例如 TJ 大神的 co 框架。

          // 上例用 Generator 實現(xiàn)// 假定 git.add, git.commit, git.push 均做了 Promise 封裝,返回一個 Promise 對象const co = require('co');const git = require('shell').git;

          co(function* (){ ?const commitMsg = '...'; ? ? ?// 共享的局部變量

          ?yield git.add('pattern/for/some/files/*'); ?yield git.commit(commitMsg); ?yield git.push();
          }).catch((err) => { ?console.log(err);
          });

          Generator 是一個 ES6 最佳的異步編程選擇么?顯然不是,因為除了基本語法外,我們還要額外去實現(xiàn)執(zhí)行器來達(dá)到執(zhí)行的目的,但是它整體的代碼結(jié)構(gòu)是優(yōu)于回調(diào)函數(shù)嵌套和 Promise 模式的。

          Async-Await

          這并不是一個 ES6 新特性,而是 ES7 的語法,放在這里是因為它將是 JavaScript 目前支持異步編程最好的方式

          // 上例用 async-await 實現(xiàn)// 假定 git.add, git.commit, git.push 均做了 Promise 封裝,返回一個 Promise 對象const git = require('shell').git;

          (async function(){ ?const commitMsg = '...'; ? ? ?// 共享的局部變量

          ?try{
          ? ?await git.add('pattern/for/some/files/*');
          ? ?await git.commit(commitMsg);
          ? ?await git.push();
          ?}catch(err){ ? ?console.log(err);
          ?}
          })();

          元編程

          元編程是指的是開發(fā)人員對 “語言本身進(jìn)行編程”。一般是編程語言暴露了一些 API,供開發(fā)人員來操作語言本身的某些特性。ES6 兩個新特性 ProxyReflect 是 JavaScript 關(guān)于對象元編程能力的擴(kuò)展。

          Proxy

          Proxy 是 ES6 加入的一個新特性,它可以 “代理” 對象的原生行為,替換為執(zhí)行自定義行為。

          這樣的元編程能力使得我們可以更輕松的擴(kuò)展出一些特殊對象。

          • 任何對象都可以被 “代理”

          • 利用 Proxy.revocable(target, handler) 可以創(chuàng)建出一個可逆的 “被代理” 對象

          // 簡單 element 選擇控制工具的實現(xiàn)const cacheElement = function(target, prop) { ?if(target.hasOwnProperty(prop)){ ? ? ?return target[prop];
          ? ?}else{ ? ? ?return target[prop] = document.getElementById(prop);
          ? ?}
          }const elControl = new Proxy(cacheElement, {
          ?get: (target, prop) => { ? ?return cacheElement(target, prop);
          ?},
          ?set: (target, prop, val) => {
          ? ?cacheElement(target, prop).textContent = val;
          ?},
          ?apply: (target, thisArg, args) => { ? ?return Reflect.ownKeys(target);
          ?}
          });

          elControl.first; ? ? // div#firstelControl.second; ? ?// div#secondelControl.first = 5; ? ?// div#first => 5elControl.second = 10; ?// div#second => 10elControl(); ? ?// ['first', 'second']

          Reflect

          ES6 中引入的 Reflect 是另一個元編程的特性,它使得我們可以直接操縱對象的原生行為。Reflect 可操縱的行為與 Proxy 可代理的行為是一一對應(yīng)的,這使得可以在 Proxy 的自定義方法中方便的使用 Reflect 調(diào)起原生行為。

          // 1. Proxy 的自定義方法中,通過 Reflect 調(diào)用原生行為const customProxy = new Proxy({ ?'custom': 1}, {
          ?get: (target, prop) => { ? ?console.log(`get ${prop} !`); ? ?return Reflect.get(target, undefined, prop);
          ?}
          });

          customProxy.custom; ?// get custom, 1// 2. 與 Object 對象上已經(jīng)開放的操作原生行為方法相比,語法更加清晰易用(例如:Object.hasOwnProperty 與 Reflect.has)const symb = Symbol('b');const a = {
          ?[symb]: 1, ?'b': 2};if(Reflect.has(a, symb) && Reflect.has(a, 'b')){ ?// good
          ?console.log('good');
          }
          Reflect.ownKeys(a); ?// ["b", Symbol(b)]

          進(jìn)階閱讀

          篇幅有限,無法面面俱到,還想再最后推薦給大家一些想進(jìn)階了解 ES6 的必看內(nèi)容

          • 如果你關(guān)注兼容性,推薦看:https://kangax.github.io/compat-table/es6/,這里介紹了從 ES5 到 ES2016+ 的所有特性(包括仍未定稿的特性)及其在各環(huán)境的兼容性

          • 如果你關(guān)注性能,推薦看:http://kpdecker.github.io/six-speed/,這里通過性能測試,將 ES6 特性的原生實現(xiàn)與 ES5 polyfill 版本進(jìn)行對比,覆蓋了各主流環(huán)境;同時也可以側(cè)面對比出各環(huán)境在原生實現(xiàn)上的性能優(yōu)劣

          • 如果你想全面了解特性,推薦看:https://developer.mozilla.org/en-US/docs/Web/JavaScript,覆蓋特性的各方面,包括全面的 API(包括不推薦和廢棄的)和基礎(chǔ)用法

          • 如果你想看特性更多的使用示例和對應(yīng)的 polyfill 實現(xiàn),推薦看:http://es6-features.org/#Constants,這里對各個特性都給出了使用豐富的例子和一個 polyfill 實現(xiàn),簡單明了

          • 如果想了解 ECMA Script 最多最全面的細(xì)節(jié),英語又比較過硬,推薦在需要時看:http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf,(或者直接看最新的:https://tc39.github.io/ecma262/)

          結(jié)語

          基礎(chǔ)篇+進(jìn)階篇基本介紹完了 ES6 的主要特性,但 ES6 僅僅是現(xiàn)在時,后續(xù)如果大家覺得這個系列有意思,可以再寫一寫 ES 2016+ 的相關(guān)內(nèi)容,來擁抱一下更新的變化。

          最后,希望文章中的部分內(nèi)容可以對大家理解和使用 ES6 有所幫助,感謝支持~

          參考資料

          • https://developer.mozilla.org/en-US/docs/Web/JavaScript

          • https://babeljs.io/docs/learn-es2015/

          • https://www.stackoverflow.com

          • http://es6.ruanyifeng.com/

          • https://kangax.github.io/compat-table/esnext/

          • http://www.ecma-international.org/ecma-262/6.0/

          推薦閱讀


          1、你不知道的前端異常處理(萬字長文,建議收藏)

          2、你不知道的 TypeScript 泛型(萬字長文,建議收藏)

          3、你不知道的 Web Workers (萬字長文,建議收藏)

          4、immutablejs 是如何優(yōu)化我們的代碼的?

          5、或許是一本可以徹底改變你刷 LeetCode 效率的題解書

          6、想去力扣當(dāng)前端,TypeScript 需要掌握到什么程度?

          7、距離弄懂正則的環(huán)視,你只差這一篇文章


          ?

          關(guān)注加加,星標(biāo)加加~

          ?

          如果覺得文章不錯,幫忙點個在看唄




          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  波多野结衣网页 | www.手机av | 国产高清毛片 | 牛牛超碰 | 理论片麻豆|