<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC] # ES Module ## 模塊導出 模塊導出只有一個關鍵詞:`export`,最簡單的方法就是在聲明的變量前面直接加上 export 關鍵詞。 ~~~ export?const?name =?'Shenfq' ~~~ 可以在 const、let、var 前直接加上 export,也可以在 function 或者 class 前面直接加上 export。 ~~~ export function getName() { return name } export class Logger { log(...args) { console.log(...args) } } ~~~ 上面的導出方法也可以使用大括號的方式進行簡寫。 ~~~ const name = 'Shenfq' function getName() { return name } class Logger { log(...args) { console.log(...args) } } export { name, getName, Logger } ~~~ 最后一種語法,也是我們經常使用的,導出默認模塊。 ~~~ const name = 'Shenfq' export default name ~~~ ## 模塊導入 模塊的導入使用`import`,并配合`from`關鍵詞。 ~~~ // main.js import name from './module.js' // module.js const name = 'Shenfq' export default name ~~~ 這樣直接導入的方式,`module.js`中必須使用`export default`,也就是說 import 語法,默認導入的是`default`模塊。如果想要導入其他模塊,就必須使用對象展開的語法。 ~~~ // main.js import { name, getName } from './module.js' // module.js export const name = 'Shenfq' export const getName = () => name ~~~ 如果模塊文件同時導出了默認模塊,和其他模塊,在導入時,也可以同時將兩者導入。 ~~~ // main.js import name, { getName } from './module.js' //module.js const name = 'Shenfq' export const getName = () => name export default name ~~~ 當然,ESM 也提供了重命名的語法,將導入的模塊進行重新命名。 ~~~ // main.js import * as mod from './module.js' let name = '' name = mod.name name = mod.getName() // module.js export const name = 'Shenfq' export const getName = () => name ~~~ 上述寫法就相當于于將模塊導出的對象進行重新賦值: ~~~ // main.js import { name, getName } from './module.js' const mod = { name, getName } ~~~ 同時也可以對單獨的變量進行重命名: ~~~ // main.js import { name, getName as getModName } ~~~ ## 導入同時進行導出 如果有兩個模塊 a 和 b ,同時引入了模塊 c,但是這兩個模塊還需要導入模塊 d,如果模塊 a、b 在導入 c 之后,再導入 d 也是可以的,但是有些繁瑣,我們可以直接在模塊 c 里面導入模塊 d,再把模塊 d 暴露出去。 ![模塊關系](https://image-static.segmentfault.com/400/999/4009990148-3706cf784198653f_articlex) ~~~ // module_c.js import { name, getName } from './module_d.js' export { name, getName } ~~~ 這么寫看起來還是有些麻煩,這里 ESM 提供了一種將 import 和 export 進行結合的語法。 ~~~ export?{ name, getName }?from?'./module_d.js' ~~~ 上面是 ESM 規范的一些基本語法,如果想了解更多,可以翻閱阮老師的[《ES6 入門》](http://es6.ruanyifeng.com/#docs/module)。 ## ESM 與 CommonJS 的差異 * CommonJS 是同步導入,因為用于服務端,文件都在本地,同步導入即使卡住主線程影響也不大。而后者是異步導入,因為用于瀏覽器,需要下載文件,如果也采用同步導入會對渲染有很大影響 * 語法上的差異,前面也已經簡單介紹過了,一個使用`import/export`語法,一個使用`require/module`語法。 * CommonJS 支持動態導入,也就是`require(${path}/xx.js)`,后者目前不支持,但是已有提案 * ESM 導入模塊的變量都是強綁定,導出模塊的變量一旦發生變化,對應導入模塊的變量也會跟隨變化,而 CommonJS 中導入的模塊都是值傳遞與引用傳遞,類似于函數傳參(基本類型進行值傳遞,相當于拷貝變量,非基礎類型【對象、數組】,進行引用傳遞)。 下面我們看下詳細的案例: **CommonJS** ~~~ // a.js const mod = require('./b') setTimeout(() => { console.log(mod) }, 1000) // b.js let mod = 'first value' setTimeout(() => { mod = 'second value' }, 500) module.exports = mod ~~~ ~~~ $ node a.js first value ~~~ **ESM** ~~~ // a.mjs import { mod } from './b.mjs' setTimeout(() => { console.log(mod) }, 1000) // b.mjs export let mod = 'first value' setTimeout(() => { mod = 'second value' }, 500) ~~~ ~~~ $ node --experimental-modules a.mjs # (node:99615) ExperimentalWarning: The ESM module loader is experimental. second value ~~~ 另外,CommonJS 的模塊實現,實際是給每個模塊文件做了一層函數包裹,從而使得每個模塊獲取`require/module`、`__filename/__dirname`變量。那上面的`a.js`來舉例,實際執行過程中`a.js`運行代碼如下: ~~~ // a.js (function(exports, require, module, __filename, __dirname) { const mod = require('./b') setTimeout(() => { console.log(mod) }, 1000) }); ~~~ 而 ESM 的模塊是通過`import/export`關鍵詞來實現,沒有對應的函數包裹,所以在 ESM 模塊中,需要使用`import.meta`變量來獲取`__filename/__dirname`。`import.meta`是 ECMAScript 實現的一個包含模塊元數據的特定對象,主要用于存放模塊的`url`,而 node 中只支持加載本地模塊,所以 url 都是使用`file:`協議。 ~~~ import url from 'url' import path from 'path' // import.meta: { url: file:///Users/dev/mjs/a.mjs } const __filename = url.fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) ~~~ ## 加載的原理 步驟: 1. Construction(構造):下載所有的文件并且解析為module records。 2. Instantiation(實例):把所有導出的變量入內存指定位置(但是暫時還不求值)。然后,讓導出和導入都指向內存指定位置。這叫做『linking(鏈接)』。 3. Evaluation(求值):執行代碼,得到變量的值然后放到內存對應位置。 ### 模塊記錄 所有的模塊化開發,都是從一個入口文件開始,無論是 Node.js 還是瀏覽器,都會根據這個入口文件進行檢索,一步一步找到其他所有的依賴文件。 ~~~ // Node.js: main.mjs import Log from './log.mjs' ~~~ ~~~ <!-- chrome、firefox --> <script type="module" src="./log.js"></script> ~~~ 值得注意的是,剛開始拿到入口文件,我們并不知道它依賴了哪些模塊,所以必須先通過 js 引擎靜態分析,得到一個模塊記錄,該記錄包含了該文件的依賴項。所以,一開始拿到的 js 文件并不會執行,只是會將文件轉換得到一個模塊記錄(module records)。所有的 import 模塊都在模塊記錄的`importEntries`字段中記錄,更多模塊記錄相關的字段可以查閱[tc39.es](https://tc39.es/ecma262/#table-38)。 ![模塊記錄](https://image-static.segmentfault.com/316/684/316684428-224c3f381640c464_articlex) ### 模塊構造 得到模塊記錄后,會下載所有依賴,并再次將依賴文件轉換為模塊記錄,一直持續到沒有依賴文件為止,這個過程被稱為『構造』(construction)。 模塊構造包括如下三個步驟: 1. 模塊識別(解析依賴模塊 url,找到真實的下載路徑); 2. 文件下載(從指定的 url 進行下載,或從文件系統進行加載); 3. 轉化為模塊記錄(module records)。 對于如何將模塊文件轉化為模塊記錄,ESM 規范有詳細的說明,但是在構造這個步驟中,要怎么下載得到這些依賴的模塊文件,在 ESM 規范中并沒有對應的說明。因為如何下載文件,在服務端和客戶端都有不同的實現規范。比如,在瀏覽器中,如何下載文件是屬于 HTML 規范(瀏覽器的模塊加載都是使用的`<script>`標簽)。 雖然下載完全不屬于 ESM 的現有規范,但在`import`語句中還有一個引用模塊的 url 地址,關于這個地址需要如何轉化,在 Node 和瀏覽器之間有會出現一些差異。簡單來說,在 Node 中可以直接 import 在 node\_modules 中的模塊,而在瀏覽器中并不能直接這么做,因為瀏覽器無法正確的找到服務器上的 node\_modules 目錄在哪里。好在有一個叫做[import-maps](https://github.com/WICG/import-maps)的提案,該提案主要就是用來解決瀏覽器無法直接導入模塊標識符的問題。但是,在該提案未被完全實現之前,瀏覽器中依然只能使用 url 進行模塊導入。 ~~~ <script type="importmap"> { "imports": { "jQuery": "/node_modules/jquery/dist/jquery.js" } } </script> <script type="module"> import $ from 'jQuery' $(function () { $('#app').html('init') }) </script> ~~~ 下載好的模塊,都會被轉化為模塊記錄然后緩存到`module map`中,遇到不同文件獲取的相同依賴,都會直接在`module map`緩存中獲取。 ~~~ // log.js const log = console.log export default log // file.js export { readFileSync as read, writeFileSync as write } from 'fs' ~~~ ![module map](https://image-static.segmentfault.com/423/593/4235939352-bc60b81e9f86602c_articlex) ### 模塊實例 獲取到所有依賴文件并建立好`module map`后,就會找到所有模塊記錄,并取出其中的所有導出的變量,然后,將所有變量一一對應到內存中,將對應關系存儲到『模塊環境記錄』(module environment record)中。當然當前內存中的變量并沒有值,只是初始化了對應關系。初始化導出變量和內存的對應關系后,緊接著會設置模塊導入和內存的對應關系,確保相同變量的導入和導出都指向了同一個內存區域,并保證所有的導入都能找到對應的導出。 ![模塊連接](https://image-static.segmentfault.com/146/031/1460314296-c2712eb7fea56a0a_articlex) 由于導入和導出指向同一內存區域,所以導出值一旦發生變化,導入值也會變化,不同于 CommonJS,CommonJS 的所有值都是基于拷貝的。連接到導入導出變量后,我們就需要將對應的值放入到內存中,下面就要進入到求值的步驟了。 ### 模塊求值 求值步驟相對簡單,只要運行代碼把計算出來的值填入之前記錄的內存地址就可以了。到這里就已經能夠愉快的使用 ESM 模塊化了。 ## ESM的進展 因為 ESM 出現較晚,服務端已有 CommonJS 方案,客戶端又有 webpack 打包工具,所以 ESM 的推廣不得不說還是十分艱難的。 ### 客戶端 我們先看看客戶端的支持情況,這里推薦大家到[Can I Use](https://caniuse.com/#feat=es6-module)直接查看,下圖是`2019/11`的截圖。 ![Can I use](https://image-static.segmentfault.com/252/323/2523237886-b5b1331b310899c3_articlex) 目前為止,主流瀏覽器都已經支持 ESM 了,只需在`script`標簽傳入指定的`type="module"`即可。 ~~~ <script type="module" src="./main.js"></script> ~~~ 另外,我們知道在 Node.js 中,要使用 ESM 有時候需要用到 .mjs 后綴,但是瀏覽器并不關心文件后綴,只需要 http 響應頭的MIME類型正確即可(`Content-Type: text/javascript`)。同時,當`type="module"`時,默認啟用`defer`來加載腳本。這里補充一張 defer、async 差異圖。 ![img](https://image-static.segmentfault.com/215/179/2151798436-59da4801c6772_articlex) 我們知道瀏覽器不支持`script`的時候,提供了`noscript`標簽用于降級處理,模塊化也提供了類似的標簽。 ~~~ <script type="module" src="./main.js"></script> <script nomodule> alert('當前瀏覽器不支持 ESM !!!') </script> ~~~ 這樣我們就能針對支持 ESM 的瀏覽器直接使用模塊化方案加載文件,不支持的瀏覽器還是使用 webpack 打包的版本。 ~~~ <script type="module" src="./src/main.js"></script> <script nomodule src="./dist/app.[hash].js"></script> ~~~ #### 預加載 我們知道瀏覽器的 link 標簽可以用作資源的預加載,比如我需要預先加載`main.js`文件: ~~~ <link rel="preload" href="./main.js"></link> ~~~ 如果這個`main.js`文件是一個模塊化文件,瀏覽器僅僅預先加載單獨這一個文件是沒有意義的,前面我們也說過,一個模塊化文件下載后還需要轉化得到模塊記錄,進行模塊實例、模塊求值這些操作,所以我們得想辦法告訴瀏覽器,這個文件是一個模塊化的文件,所以瀏覽器提供了一種新的 rel 類型,專門用于模塊化文件的預加載。 ~~~ <link rel="modulepreload" href="./main.js"></link> ~~~ #### 現狀 雖然主流瀏覽器都已經支持了 ESM,但是根據[chrome 的統計](https://www.chromestatus.com/metrics/feature/timeline/popularity/2062),有用到`<script type="module">`的頁面只有 1%。截圖時間為`2019/11`。 ![統計](https://image-static.segmentfault.com/361/915/3619154756-91784f64ed63265d_articlex) ### 服務端 瀏覽器能夠通過 script 標簽指定當前腳本是否作為模塊處理,但是在 Node.js 中沒有很明確的方式來表示是否需要使用 ESM,而且 Node.js 中本身就已經有了 CommonJS 的標準模塊化方案。就算開啟了 ESM,又通過何種方式來判斷當前入口文件導入的模塊到底是使用的 ESM 還是 CommonJS 呢?為了解決上述問題,node 社區開始出現了 ESM 的相關草案,具體可以在[github](https://github.com/nodejs/node-eps/blob/master/002-es-modules.md)上查閱。 2017年發布的 Node.js 8.5.0 開啟了 ESM 的實驗性支持,在啟動程序時,加上`--experimental-modules`來開啟對 ESM 的支持,并將`.mjs`后綴的文件當做 ESM 來解析。早期的期望是在 Node.js 12 達到 LTS 狀態正式發布,然后期望并沒有實現,直到最近的 13.2.0 版本才正式支持 ESM,也就是取消了`--experimental-modules`啟動參數。具體細節可以查看 Node.js 13.2.0 的[官方文檔](https://nodejs.org/api/esm.html#esm_ecmascript_modules)。 關于`.mjs`后綴社區有兩種完全不同的態度。支持的一方認為通過文件后綴區分類型是最簡單也是最明確的方式,且社區早已有類似案例,例如,`.jsx`用于 React 組件、`.ts`用于 ts 文件;而支持的一方認為,`.js`作為 js 后綴已經存在這么多年,視覺上很難接受一個`.mjs`也是 js 文件,而且現有的很多工具都是以`.js`后綴來識別 js 文件,如果引入了`.mjs`方案,就有大批量的工具需要修改來有效的適配 ESM。 所以除了`.mjs`后綴指定 ESM 外,還可以使用`pkg.json`文件的`type`屬性。如果 type 屬性為 module,則表示當前模塊應使用 ESM 來解析模塊,否則使用 CommonJS 解析模塊。 ~~~ { "type": "module" // module | commonjs(default) } ~~~ 當然有些本地文件是沒有`pkg.json`的,但是你又不想使用`.mjs`后綴,這時候只需要在命令行加上一個啟動參數`--input-type=module`。同時`input-type`也支持 commonjs 參數來指定使用 CommonJS(`-—input-type=commonjs`)。 總結一下,Node.js 中,以下三種情況會啟用 ESM 的模塊加載方式: 1. 文件后綴為`.mjs`; 2. `pkg.json`中 type 字段指定為`module`; 3. 啟動參數添加`--input-type=module`。 同樣,也有三種情況會啟用 CommonJS 的模塊加載方式: 1. 文件后綴為`.cjs`; 2. `pkg.json`中 type 字段指定為`commonjs`; 3. 啟動參數添加`--input-type=commonjs`。 雖然 13.2 版本去除了`--experimental-modules`的啟動參數,但是按照文檔的說法,在 Node.js 中使用 ESM 依舊是實驗特性。 > [Stability: 1](https://nodejs.org/api/documentation.html#documentation_stability_index)\- Experimental 不過,相信等到 Node.js 14 LTS 版本發布時,ESM 的支持應該就能進入穩定階段了,這里還有一個 Node.js 關于 ESM 的整個[計劃列表](https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md)可以查閱。 ~~~ // 引入模塊 API import XXX from './a.js' import { XXX } from './a.js' // 導出模塊 API export function a() {} export default function() {} ~~~ # 參考資料 * [模塊化的今生](https://segmentfault.com/a/1190000021167250)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看