<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
很多程式語言都有模組這一概念,JavaScript 也不例外,但在 ECMAScript 2015 規範釋出之前,JavaScript 沒有語言層面的模組語法。模組實際上是一種程式碼重用機制,要實現程式碼重用,將不同的功能劃分到不同的檔案中是必不可少的,如何在其他的檔案中使用這些檔案定義的功能呢?在 ECMAScript 2015 之前,web 開發人員不得不尋求 JavaScript 語法之外的解決方法,例如:SystemJS、RequireJS 等模組載入工具,也有開發人員使用 webpack、Browserify 等模組打包工具。ECMAScript 2015 釋出之後,JavaScript 擁有了語言層面的模組語法,它被稱為 ECMAScript modules,簡稱 ES modules,這使 web 開發人員很容易就能建立模組,使用模組。
在本文中會介紹 ES modules 的基本用法、ES modules 的特點以及在瀏覽器中使用 ES 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!'
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 匯入模組,當使用絕對路徑匯入模組時,是否能省略副檔名,這與伺服器設定相關。
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 {} // 將這個檔案標記成模組
在模組中的宣告的變數是針對該模組的,這意味著在模組中宣告的任何變數對其他模組都不可用,除非它們被顯式地匯出。
注意:由於 JavaScript 執行時會區別對待模組和常規的 JavaScript 指令碼,所以在寫程式碼的時候做好顯示地標記 JavaScript 檔案是模組,只要 JavaScript 檔案中包含 export 或者 import 關鍵字,JavaScript 執行時就會認為這個檔案是模組
在這部分介紹的這 5 個差異是與 JavaScript 執行環境無關的差異,在之後的部分會介紹在瀏覽器中使用 ES modules,這那裡會補充一些新的差異。
現代瀏覽器支援 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,我們可以在 script 標籤上新增 defer 屬性,使瀏覽器在下載指令碼期間不暫停解析 HTML。當下載模組指令碼時,瀏覽器預設模組指令碼是 defer 的。
下圖展示了瀏覽器獲取外部模組指令碼和常規指令碼的流程
上圖源於 (html.spec.whatwg.org/multipage/s…)
模組指令碼以及它的依賴項是通過 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規範的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45