您好,登錄后才能下訂單哦!
這篇文章主要講解了“ECMAScript modules規范怎么寫”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“ECMAScript modules規范怎么寫”吧!
ES modules 是 JavaScript 的標準模塊系統,模塊是一個簡單的 JavaScript 文件,在這個文件中包含 export 或者 import 關鍵字。export 用于將模塊中聲明的內容導出,import 用于從其他模塊中導入。
模塊導出用到的關鍵字是 export,它只能在模塊頂層使用。模塊可以導出函數、類、或其他基本類型等。模塊導出有4種寫法
默認導出
export default function myFunc() {} export default function () {} export default class MyClass {} export { foo as default } export default 'Hello Es modules!'
行內命名導出
export function myFunc() {} export class MyClass {} export const fooStr = 'Hello Es modules!'
通過一個 export 子句批量命名導出
function myFunc() {} class MyClass {} const fooStr = 'Hello Es modules!' export {myFunc, MyClass , fooStr } // 在這個地方一次性導出多個
重新導出
// 重新導出 other_module 中除默認導出之外的內容 export * from './other_module.js' // 重新導出 other_module 中的默認導出 export { default } from './other_module.js' // 重新導出 other_module 中的默認導出,并且將 other_module 中的 sayName 命名為 getName 之后再導出 export { default, sayName as getName } from './other_module.js'
雖然模塊導出有4種寫法,但是只有兩種方式,一種默認導出,另一種是命名導出,在同一個模塊中命名導出可以有多個,默認導出只能有一個,這兩種方式可以混合使用。
在軟件開發的過程中,通常有多種寫法能到達同一目的,但并不是每一種寫法都值得推薦,模塊導出也是類似的。如果在同一個模塊中,即有默認導出,又有行內命名導出,還有 export 子句批量命名導出,那么你的模塊很可能會變得混亂。在這里我推薦使用默認導出,并且將 export default 放在模塊的末尾。如果你必須要命名導出,我推薦使用export 子句批量命名導出,并將 export 子句放在文件的末尾。
介紹完模塊導出之后,按理說應該介紹模塊導入,但我決定先介紹模塊說明符,這是因為模塊導入依賴模塊說明符。說明符是字符串字面值,它表示導入模塊的路徑,說明符一共有三種類型,分別是:相對路徑、絕對路徑和 bare(裸 露) 模式。
相對路徑
import foo from './myModule.js' import { sayName } from '../other_module.js'
相對路徑說明符以 / 、./ 、../ 開頭,當使用相對路徑說明符時不能省略文件的擴展名。在 web 項目開發中使用相對路徑導入模塊的時候,你可能省略了文件擴展名,它還是能夠工作,那是因為你的項目使用了如 webpack 這樣的模塊打包工具。
絕對路徑
import React from 'https://cdn.skypack.dev/react'
上述代碼表示從 cdn 導入模塊,當使用絕對路徑導入模塊時,是否能省略文件擴展名,這與服務器配置相關。
bare(裸 露) 模式
import React from 'react' import Foo from 'react/lib.js'
bare 模式從 node_module 中導入模塊,在 web 項目開發中,用這種說明符導入模塊很常見,但是 ES modules 并不支持它,在你的項目中,你之所以能夠使用它,是因為你的項目用了如 webpack 這樣的模塊打包工具。
到目前為止,我已經介紹完了3種模塊說明符,ES modules 只支持其中兩種,分別是:相對路徑和絕對路徑。
模塊導入用到的關鍵字是 import,import 與 export 一樣只能在模塊頂部使用,模塊說明符不能包含變量,它必須是固定的字符串字面量。模塊導入有6種不同的寫法,如下:
默認導入
// 你可以將 myFunc 改成任何你喜歡的變量名 import myFunc from './myModule.js'
將模塊作為一個對象導入(即命名空間導入)
import * as api from './myModule.js' // 通過對象的 default 屬性訪問 myModule.js 中的默認導出 console.log(api.default)
命名導入
// 導入 myModule.js 中的fooStr import { fooStr } from './myModule.js' // 將myModule.js中默認導出命名為myFunc import { default as myFunc } './myModule.js' // 將 myModule.js中的 fooStr 命名為 myStr import { fooStr as myStr } from './myModule.js'
當某個模塊中導出了很多內容,而你只需要用到它導出的一部分內容,你可以使用這個寫法只導入你需要的部分,在做搖樹優化的時候這至關重要。
只加載模塊,不導入任何東西
import './myModule.js'
不會將 myModule.js 中的任何內容導入到當前模塊,但是會執行 myModule.js 模塊體,這通常用于執行一些初始化操作。
將默認導入與命名導入混合使用
import myFunc, { fooStr } from './myModule.js'
將默認導入與命名空間導入混合使用
import myFunc, * as api from './myModule.js'
補充:同一個模塊可以被多次導入,但是它的模塊體只會執行一次
例如有個模塊 A,它導出了一個變量 count,模塊 B 導入模塊 A 的 count,count 對模塊 B 而言是只讀的,所以在模塊 B 中不能直接修改 count,下面用代碼演示一下:
// 模塊A的代碼如下: export var count = 0 // 注意:這里用的是 var 關鍵字 // 模塊B的代碼如下: import { count } from './moduleA.js' count++ // Uncaught TypeError: Assignment to constant variable
將上述代碼放在瀏覽器中運行,瀏覽器會報錯,錯誤類型是:TypeError。如果模塊 A 導出了對象 obj,在模塊 B 中不能直接給 obj 賦值,但是可以增、刪、改 obj 中的屬性。
現在我已經介紹了只讀的含義,下面介紹引用的含義。引用意味著在項目中多個模塊用的是同一個變量,例如:模塊 B 和模塊 C 都導入了模塊 A 的 count 和 changeCount 函數,模塊 B 通過 changeCount 修改了 count 的值,模塊C中的 count 會被一同修改,代碼如下:
// 模塊A的代碼如下: export var count = 0 export function changeCount() { count++ } // 模塊B的代碼如下: import { count, changeCount } from './moduleA.js' changeCount () console.log(count) // 1 // 模塊C的代碼如下: import { count } from './moduleA.js' console.log(count) // 1
模塊 B 和模塊 C 導入的是引用,而非副本,模塊導出的變量在整個項目中是一個單例。
循環依賴指的是兩個模塊相互依賴,比如模塊 A 導入了模塊 B,模塊 B 又導入了模塊 A。盡管 ES modules 支持循環依賴,但應該避免,因為這會使兩個模塊強耦合。ES modules 支持循環依賴這是因為導入是導出的只讀引用。
如果你知道 JavaScript 函數提升,那么你很容易理解 ES modules 的導入提升。由于 ES modules 的導入會被提升到模塊作用域的開頭,所以你不需要先導入再使用。下面的代碼可以工作:
foo() import foo from './myModule.js'
導出必須位于模塊的頂層這一點毋庸置疑,在 ECMAScript 2020 規范中添加了動態導入,它使模塊導入可以不必位于模塊的頂層。在后面會單獨介紹動態導入,在這里介紹的是靜態導入。
ECMAScript 2020 之前,JavaScript 的 ES modules 是一個靜態模塊系統,它意味著模塊的依賴項在你寫代碼的時候就確定了,不用等到代碼運行階段才確定,這讓代碼打包工具,如 webpack,很容易就能分析出 ES 模塊中的依賴,給搖樹優化提供了便利。
即便 ECMAScript 2020 增加了動態導入,靜態導入與動態導入在寫法上有差異,靜態導入使用 import 關鍵字,動態導入使用 import()。靜態導入只能位于模塊頂層。
模塊運行在嚴格模式下
模塊具備詞法頂部作用域
這句話的意思是,在模塊中創建的變量,如:foo,不能通過 window.foo 訪問。代碼如下:
var foo = 'hi' console.log(window.foo) // undefined console.log(foo) // hi export {} // 將這個文件標記成模塊
在模塊中的聲明的變量是針對該模塊的,這意味著在模塊中聲明的任何變量對其他模塊都不可用,除非它們被顯式地導出。
模塊中的 this 關鍵字沒有指向全局 this,它是 undefined,如果要在模塊中訪問全局 this 要使用 globalThis,在瀏覽器中 globalThis 是 window 對象。
export 和靜態導入 import 只能在模塊中使用
在模塊頂層能使用 await 關鍵字,在常規 JavaScript 腳本中只能在 async 函數中使用 await 關鍵字
注意:由于 JavaScript 運行時會區別對待模塊和常規的 JavaScript 腳本,所以在寫代碼的時候做好顯示地標記 JavaScript 文件是模塊,只要 JavaScript 文件中包含 export 或者 import 關鍵字,JavaScript 運行時就會認為這個文件是模塊
現代瀏覽器支持 ES modules,你可以將 script 標簽的 type 屬性設置為 module 來告訴瀏覽器這個腳本是模塊,代碼如下:
<!--外部模塊--> <script type="module" src="./module.js"></script> <!--內聯模塊--> <script type="module"> import {count} from './moduleA.js'; import React from 'https://cdn.skypack.dev/react' console.log(count, React) </script>
出于對兼容性的考慮,可能還需要 <script nomodule src=’xxx.js’></script>
,在這里不做介紹。
在之前介紹了模塊和常規 JavaScript 腳本與運行環境無關的差異,現在來介紹在瀏覽器環境中二者的差異
模塊只會被執行一次
不管模塊被引入了多少次,它只會被執行一次,而常規的 JavaScript 腳本執行次數與它被添加到 DOM 的次數一致,添加多少次就執行多少次。比如有下面一段代碼:
<!--外部模塊--> <script type="module" src="./module.js"></script> <script type="module" src="./module.js"></script> <script type="module" src="./module.js"></script> <!--內聯模塊--> <script type="module"> import { count } from './module.js'; </script> <script src='./classic.js'></script> <script src='./classic.js'></script>
在上述代碼中 module.js 只會被執行一次,classic.js 會被執行兩次
下載模塊腳本不會阻塞 HTML 解析
默認情況,當瀏覽器下載常規外部腳本時,它會暫停解析 HTML,我們可以在 script 標簽上添加 defer 屬性,使瀏覽器在下載腳本期間不暫停解析 HTML。當下載模塊腳本時,瀏覽器默認模塊腳本是 defer 的。
下圖展示了瀏覽器獲取外部模塊腳本和常規腳本的流程
模塊腳本通過 CORS 獲取
模塊腳本以及它的依賴項是通過 CORS 獲取的,當獲取一個跨域的模塊腳本時需要特別注意這個問題,跨域腳本的響應頭 Access-Control-Allow-Origin 必須包含當前域,否則模塊會獲取失敗,而獲取常規腳本則沒有這個限制。
為了保證獲取同源模塊腳本時,瀏覽器始終帶上 credentials(cookie 等),推薦給 script 標簽加上 crossorigin 屬性。
到目前為止介紹的都是靜態導入模塊,靜態導入必須等模塊代碼全部下載之后才會執行程序,這可能會使網站的首屏渲染性能下降。通過動態導入模塊可以根據用戶在界面上的操作按需下載資源,節省流量,動態導入在 ECMAScript 2020 正式發布,它需要用到import(),用法如下所示:
// 通過相對路徑導入 import('./exportDefault.js').then((module) => { console.log(module) // line A }) // 通過絕對路徑導入 import('https://cdn.skypack.dev/react').then((react) => { console.log(react) // line B })
從上述代碼可以看出 import() 的返回值是一個 promise 對象,當模塊加載成功之后 promise 對象的狀態會變成 fulfilled,import() 可以與 async/await 配合使用
上述代碼中的 line A 和 line B 標識的變量 module 和 react 都是 JavaScript 對象,我們可以用對象的點語法和中括號語法訪問模塊導出的任何方法和屬性,模塊的默認導出通過 default 屬性名訪問。
動態導入與靜態導入存在如下 3 個差異:
動態導入的模塊說明符可以是變量,但靜態導入的模塊說明符只能是字符串字面量
動態導入能在模塊和常規腳本中使用,但是靜態導入只能在模塊中使用
動態導入不必位于文件的頂層,但靜態導入只能位于模塊的頂層
雖然動態導入模塊和靜態導入模塊存在差異,但它們都通過 CORS 獲取模塊腳本,所以在獲取跨域模塊腳本時,腳本的 Access-Control-Allow-Origin 響應頭一定要配置正確。
動態導入和靜態導入有它們各自的使用場景。在初始渲染時要用到的模塊使用靜態導入,其他情況,特別是那些與用戶操作相關的功能,可以使用動態導入按需加載依賴的模塊,這種做法能提高首屏渲染性能,但是會降低用戶操作過程中的性能。所以,哪些模塊使用靜態導入,哪些模塊使用動態導入需要你根據實際情況考慮。
提示:在動態導入模塊時要用到 import(),看上去這像是函數調用,實際上它并不是函數調用,而是一種特殊的語法,你不能使用 import.call()、import.apply()、const myImport = import; myImport()。
感謝各位的閱讀,以上就是“ECMAScript modules規范怎么寫”的內容了,經過本文的學習后,相信大家對ECMAScript modules規范怎么寫這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。