最近2018中文字幕在日韩欧美国产成人片_国产日韩精品一区二区在线_在线观看成年美女黄网色视频_国产精品一区三区五区_国产精彩刺激乱对白_看黄色黄大色黄片免费_人人超碰自拍cao_国产高清av在线_亚洲精品电影av_日韩美女尤物视频网站

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
帶你了解前端模塊化的今生

背景

成都創(chuàng)新互聯(lián)公司是一家朝氣蓬勃的網(wǎng)站建設公司。公司專注于為企業(yè)提供信息化建設解決方案。從事網(wǎng)站開發(fā),網(wǎng)站制作,網(wǎng)站設計,網(wǎng)站模板,微信公眾號開發(fā),軟件開發(fā),微信平臺小程序開發(fā),10余年建站對成都履帶攪拌車等多個行業(yè),擁有多年的網(wǎng)站運維經(jīng)驗。

眾所周知,早期 JavaScript 原生并不支持模塊化,直到 2015 年,TC39 發(fā)布 ES6,其中有一個規(guī)范就是 ES modules(為了方便表述,后面統(tǒng)一簡稱 ESM)。但是在 ES6 規(guī)范提出前,就已經(jīng)存在了一些模塊化方案,比如 CommonJS(in Node.js)、AMD。ESM 與這些規(guī)范的共同點就是都支持導入(import)和導出(export)語法,只是其行為的關鍵詞也一些差異。

CommonJS 

 
 
 
 
  1. // add.js  
  2. const add = (a, b) => a + b  
  3. module.exports = add  
  4. // index.js  
  5. const add = require('./add')  
  6. add(1, 5) 

AMD 

 
 
 
 
  1. // add.js  
  2. define(function() {  
  3.   const add = (a, b) => a + b  
  4.   return add  
  5. })  
  6. // index.js  
  7. require(['./add'], function (add) {  
  8.   add(1, 5)  
  9. }) 

ESM 

 
 
 
 
  1. // add.js  
  2. const add = (a, b) => a + b  
  3. export default add  
  4. //index.js  
  5. import add from './add'  
  6. add(1, 5) 

關于 JavaScript 模塊化出現(xiàn)的背景在上一章(《前端模塊化的前世》))已經(jīng)有所介紹,這里不再贅述。但是 ESM 的出現(xiàn)不同于其他的規(guī)范,因為這是 JavaScript 官方推出的模塊化方案,相比于 CommonJS 和 AMD 方案,ESM采用了完全靜態(tài)化的方式進行模塊的加載。

ESM規(guī)范

模塊導出

模塊導出只有一個關鍵詞:export,最簡單的方法就是在聲明的變量前面直接加上 export 關鍵詞。 

 
 
 
 
  1. export const name = 'Shenfq' 

可以在 const、let、var 前直接加上 export,也可以在 function 或者 class 前面直接加上 export。 

 
 
 
 
  1. export function getName() {  
  2.   return name  
  3. }  
  4. export class Logger {  
  5.     log(...args) {  
  6.     console.log(...args)  
  7.   }  

上面的導出方法也可以使用大括號的方式進行簡寫。 

 
 
 
 
  1. const name = 'Shenfq'  
  2. function getName() {  
  3.   return name  
  4. }  
  5. class Logger {  
  6.     log(...args) {  
  7.     console.log(...args)  
  8.   }  
  9. }  
  10. export { name, getName, Logger } 

最后一種語法,也是我們經(jīng)常使用的,導出默認模塊。 

 
 
 
 
  1. const name = 'Shenfq'  
  2. export default name 

模塊導入

模塊的導入使用import,并配合 from 關鍵詞。 

 
 
 
 
  1. // main.js  
  2. import name from './module.js'  
  3. // module.js  
  4. const name = 'Shenfq'  
  5. export default name 

這樣直接導入的方式,module.js 中必須使用 export default,也就是說 import 語法,默認導入的是default模塊。如果想要導入其他模塊,就必須使用對象展開的語法。 

 
 
 
 
  1. // main.js  
  2. import { name, getName } from './module.js'  
  3. // module.js  
  4. export const name = 'Shenfq'  
  5. export const getName = () => name 

如果模塊文件同時導出了默認模塊,和其他模塊,在導入時,也可以同時將兩者導入。 

 
 
 
 
  1. // main.js  
  2. import name, { getName } from './module.js'  
  3. //module.js  
  4. const name = 'Shenfq'  
  5. export const getName = () => name  
  6. export default name 

當然,ESM 也提供了重命名的語法,將導入的模塊進行重新命名。 

 
 
 
 
  1. // main.js  
  2. import * as mod from './module.js'  
  3. let name = ''  
  4. name = mod.name  
  5. name = mod.getName()  
  6. // module.js  
  7. export const name = 'Shenfq'  
  8. export const getName = () => name 

上述寫法就相當于于將模塊導出的對象進行重新賦值: 

 
 
 
 
  1. // main.js  
  2. import { name, getName } from './module.js'  
  3. const mod = { name, getName } 

同時也可以對單獨的變量進行重命名: 

 
 
 
 
  1. // main.js  
  2. import { name, getName as getModName } 

導入同時進行導出

如果有兩個模塊 a 和 b ,同時引入了模塊 c,但是這兩個模塊還需要導入模塊 d,如果模塊 a、b 在導入 c 之后,再導入 d 也是可以的,但是有些繁瑣,我們可以直接在模塊 c 里面導入模塊 d,再把模塊 d 暴露出去。

 

 
 
 
 
  1. // module_c.js  
  2. import { name, getName } from './module_d.js'  
  3. export { name, getName } 

這么寫看起來還是有些麻煩,這里 ESM 提供了一種將 import 和 export 進行結合的語法。 

 
 
 
 
  1. export { name, getName } from './module_d.js' 

上面是 ESM 規(guī)范的一些基本語法,如果想了解更多,可以翻閱阮老師的 《ES6 入門》。

ESM 與 CommonJS 的差異

首先肯定是語法上的差異,前面也已經(jīng)簡單介紹過了,一個使用 import/export 語法,一個使用 require/module 語法。

另一個 ESM 與 CommonJS 顯著的差異在于,ESM 導入模塊的變量都是強綁定,導出模塊的變量一旦發(fā)生變化,對應導入模塊的變量也會跟隨變化,而 CommonJS 中導入的模塊都是值傳遞與引用傳遞,類似于函數(shù)傳參(基本類型進行值傳遞,相當于拷貝變量,非基礎類型【對象、數(shù)組】,進行引用傳遞)。

下面我們看下詳細的案例:

CommonJS 

 
 
 
 
  1. // a.js  
  2. const mod = require('./b')  
  3. setTimeout(() => {  
  4.   console.log(mod)  
  5. }, 1000)  
  6. // b.js  
  7. let mod = 'first value'  
  8. setTimeout(() => {  
  9.   mod = 'second value'  
  10. }, 500)  
  11. modmodule.exports = mod  
 
 
 
 
  1. $ node a.js  
  2. first value 

ESM 

 
 
 
 
  1. // a.mjs  
  2. import { mod } from './b.mjs'  
  3. setTimeout(() => {  
  4.   console.log(mod)  
  5. }, 1000)  
  6. // b.mjs  
  7. export let mod = 'first value'  
  8. setTimeout(() => {  
  9.   mod = 'second value'  
  10. }, 500)  
 
 
 
 
  1. $ node --experimental-modules a.mjs  
  2. # (node:99615) ExperimentalWarning: The ESM module loader is experimental.  
  3. second value 

另外,CommonJS 的模塊實現(xiàn),實際是給每個模塊文件做了一層函數(shù)包裹,從而使得每個模塊獲取 require/module、__filename/__dirname 變量。那上面的 a.js 來舉例,實際執(zhí)行過程中 a.js 運行代碼如下: 

 
 
 
 
  1. // a.js  
  2. (function(exports, require, module, __filename, __dirname) {  
  3.     const mod = require('./b')  
  4.   setTimeout(() => {  
  5.     console.log(mod)  
  6.   }, 1000)  
  7. }); 

而 ESM 的模塊是通過 import/export 關鍵詞來實現(xiàn),沒有對應的函數(shù)包裹,所以在 ESM 模塊中,需要使用 import.meta 變量來獲取 __filename/__dirname。import.meta 是 ECMAScript 實現(xiàn)的一個包含模塊元數(shù)據(jù)的特定對象,主要用于存放模塊的 url,而 node 中只支持加載本地模塊,所以 url 都是使用 file: 協(xié)議。 

 
 
 
 
  1. import url from 'url'  
  2. import path from 'path'  
  3. // import.meta: { url: file:///Users/dev/mjs/a.mjs }  
  4. const __filename = url.fileURLToPath(import.meta.url)  
  5. const __dirname = path.dirname(__filename) 

加載的原理 

步驟:

  1.  Construction(構造):下載所有的文件并且解析為module records。
  2.  Instantiation(實例):把所有導出的變量入內(nèi)存指定位置(但是暫時還不求值)。然后,讓導出和導入都指向內(nèi)存指定位置。這叫做『linking(鏈接)』。
  3.  Evaluation(求值):執(zhí)行代碼,得到變量的值然后放到內(nèi)存對應位置。

模塊記錄

所有的模塊化開發(fā),都是從一個入口文件開始,無論是 Node.js 還是瀏覽器,都會根據(jù)這個入口文件進行檢索,一步一步找到其他所有的依賴文件。 

 
 
 
 
  1. // Node.js: main.mjs  
  2. import Log from './log.mjs'  
 
 
 
 
  1.  
  2.  

值得注意的是,剛開始拿到入口文件,我們并不知道它依賴了哪些模塊,所以必須先通過 js 引擎靜態(tài)分析,得到一個模塊記錄,該記錄包含了該文件的依賴項。所以,一開始拿到的 js 文件并不會執(zhí)行,只是會將文件轉換得到一個模塊記錄(module records)。所有的 import 模塊都在模塊記錄的 importEntries 字段中記錄,更多模塊記錄相關的字段可以查閱tc39.es。

模塊構造

得到模塊記錄后,會下載所有依賴,并再次將依賴文件轉換為模塊記錄,一直持續(xù)到?jīng)]有依賴文件為止,這個過程被稱為『構造』(construction)。

模塊構造包括如下三個步驟:

  1.  模塊識別(解析依賴模塊 url,找到真實的下載路徑);
  2.  文件下載(從指定的 url 進行下載,或從文件系統(tǒng)進行加載);
  3.  轉化為模塊記錄(module records)。

對于如何將模塊文件轉化為模塊記錄,ESM 規(guī)范有詳細的說明,但是在構造這個步驟中,要怎么下載得到這些依賴的模塊文件,在 ESM 規(guī)范中并沒有對應的說明。因為如何下載文件,在服務端和客戶端都有不同的實現(xiàn)規(guī)范。比如,在瀏覽器中,如何下載文件是屬于 HTML 規(guī)范(瀏覽器的模塊加載都是使用的   

  •   
  •     import $ from 'jQuery'  
  •   $(function () {  
  •     $('#app').html('init')  
  •   })  
  •  
  • 下載好的模塊,都會被轉化為模塊記錄然后緩存到 module map 中,遇到不同文件獲取的相同依賴,都會直接在 module map 緩存中獲取。 

     
     
     
     
    1. // log.js  
    2. const log = console.log  
    3. export default log  
    4. // file.js  
    5. export {   
    6.   readFileSync as read,  
    7.   writeFileSync as write  
    8. } from 'fs' 

    模塊實例

    獲取到所有依賴文件并建立好 module map 后,就會找到所有模塊記錄,并取出其中的所有導出的變量,然后,將所有變量一一對應到內(nèi)存中,將對應關系存儲到『模塊環(huán)境記錄』(module environment record)中。當然當前內(nèi)存中的變量并沒有值,只是初始化了對應關系。初始化導出變量和內(nèi)存的對應關系后,緊接著會設置模塊導入和內(nèi)存的對應關系,確保相同變量的導入和導出都指向了同一個內(nèi)存區(qū)域,并保證所有的導入都能找到對應的導出。

    模塊連接

    由于導入和導出指向同一內(nèi)存區(qū)域,所以導出值一旦發(fā)生變化,導入值也會變化,不同于 CommonJS,CommonJS 的所有值都是基于拷貝的。連接到導入導出變量后,我們就需要將對應的值放入到內(nèi)存中,下面就要進入到求值的步驟了。

    模塊求值

    求值步驟相對簡單,只要運行代碼把計算出來的值填入之前記錄的內(nèi)存地址就可以了。到這里就已經(jīng)能夠愉快的使用 ESM 模塊化了。

    ESM的進展

    因為 ESM 出現(xiàn)較晚,服務端已有 CommonJS 方案,客戶端又有 webpack 打包工具,所以 ESM 的推廣不得不說還是十分艱難的。

    客戶端

    我們先看看客戶端的支持情況,這里推薦大家到 Can I Use 直接查看,下圖是 2019/11的截圖。

    目前為止,主流瀏覽器都已經(jīng)支持 ESM 了,只需在 script 標簽傳入指定的 type="module" 即可。 

     
     
     
     
    1.  

    另外,我們知道在 Node.js 中,要使用 ESM 有時候需要用到 .mjs 后綴,但是瀏覽器并不關心文件后綴,只需要 http 響應頭的MIME類型正確即可(Content-Type: text/javascript)。同時,當 type="module" 時,默認啟用 defer 來加載腳本。這里補充一張 defer、async 差異圖。

    我們知道瀏覽器不支持 script 的時候,提供了 noscript 標簽用于降級處理,模塊化也提供了類似的標簽。 

     
     
     
     
    1.   
    2.   
    3.   alert('當前瀏覽器不支持 ESM ?。。?)  
    4.  

    這樣我們就能針對支持 ESM 的瀏覽器直接使用模塊化方案加載文件,不支持的瀏覽器還是使用 webpack 打包的版本。 

     
     
     
     
    1.   
    2.  

    預加載

    我們知道瀏覽器的 link 標簽可以用作資源的預加載,比如我需要預先加載 main.js 文件: 

     
     
     
     
    1.  

    如果這個 main.js 文件是一個模塊化文件,瀏覽器僅僅預先加載單獨這一個文件是沒有意義的,前面我們也說過,一個模塊化文件下載后還需要轉化得到模塊記錄,進行模塊實例、模塊求值這些操作,所以我們得想辦法告訴瀏覽器,這個文件是一個模塊化的文件,所以瀏覽器提供了一種新的 rel 類型,專門用于模塊化文件的預加載。 

     
     
     
     
    1.  

    現(xiàn)狀

    雖然主流瀏覽器都已經(jīng)支持了 ESM,但是根據(jù) chrome 的統(tǒng)計,有用到