自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

快速了解JavaScript的模塊

開(kāi)發(fā) 前端
隨著現(xiàn)代 JavaScript 開(kāi)發(fā) Web 應(yīng)用變得復(fù)雜,命名沖突和依賴關(guān)系也變得難以處理,因此需要模塊化。

概述

[[436275]]

隨著現(xiàn)代 JavaScript 開(kāi)發(fā) Web 應(yīng)用變得復(fù)雜,命名沖突和依賴關(guān)系也變得難以處理,因此需要模塊化。而引入模塊化,可以避免命名沖突、方便依賴關(guān)系管理、提高了代碼的復(fù)用性和和維護(hù)性,因此,在 JavaScript 沒(méi)有模塊功能的前提下,只能通過(guò)第三方規(guī)范實(shí)現(xiàn)模塊化:

  • CommonJS:同步模塊定義,用于服務(wù)器端。
  • AMD:異步模塊定義, 用于瀏覽器端。
  • CMD:異步模塊定義,用于瀏覽器端。
  • UMD:統(tǒng)一 COmmonJS 和 AMD 模塊化方案的定義。

它們都是基于 JavaScript 的語(yǔ)法和詞法特性 “偽造” 出類似模塊的行為。而 TC-39 在 ECMAScript 2015 中加入了模塊規(guī)范,簡(jiǎn)化了上面介紹的模塊加載器,原生意味著可以取代上述的規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案,比使用庫(kù)更有效率。而 ES6 的模塊化的設(shè)計(jì)目標(biāo):

  • 像 CommonJS 一樣簡(jiǎn)單的語(yǔ)法。
  • 模塊必須是靜態(tài)的結(jié)構(gòu)
  • 支持模塊的 異步加載 和 同步加載,能同時(shí)用在 server 和 client 端
  • 支持模塊加載的 ‘靈活配置’
  • 更好地支持模塊之間的循環(huán)引用
  • 擁有語(yǔ)言層面的支持,超越 CommonJS 和 AMD

ECMAScript 在 2015 年開(kāi)始支持模塊標(biāo)準(zhǔn),此后逐漸發(fā)展,現(xiàn)已經(jīng)得到了所有主流瀏覽器的支持。ECMAScript 2015 版本也被稱為 ECMAScript 6。

模塊

ES6 模塊借用了 CommonJS 和 AMD 的很多優(yōu)秀特性,如下所示:

  • 模塊代碼只在加載后執(zhí)行。
  • 模塊只能加載一次。
  • 模塊是單例。
  • 模塊可以定義公共接口,其他模塊可以基于這個(gè)公共接口觀察和交互。
  • 模塊可以請(qǐng)求加載其他模塊。
  • 支持循環(huán)依賴。

ES6 模塊系統(tǒng)也增加了一些新行為。

  • ES6 模塊默認(rèn)在嚴(yán)格模式下執(zhí)行。
  • ES6 模塊不共享全局命名空間。
  • 模塊頂級(jí) this 的值是 undefined;常規(guī)腳本中是 window。
  • 模塊中的 var 聲明不會(huì)添加到 window 對(duì)象。
  • ES6 模塊是異步加載和執(zhí)行的。

瀏覽器運(yùn)行時(shí)在知道應(yīng)該把某個(gè)文件當(dāng)成模塊時(shí),會(huì)有條件地按照上述 ES6 模塊行為來(lái)施加限制。與 <script type="module"> 關(guān)聯(lián)或者通過(guò) import 語(yǔ)句加載的 JavaScript 文件會(huì)被認(rèn)定為模塊。

 

 

導(dǎo)出

ES6 模塊內(nèi)部的所有變量,外部無(wú)法獲取,因此提供了 export 關(guān)鍵字從模塊中導(dǎo)出實(shí)時(shí)綁定的函數(shù)、對(duì)象或原始值,這樣其他程序可以通過(guò) import 關(guān)鍵字使用它們。export 支持兩種導(dǎo)出方式:命名導(dǎo)出和默認(rèn)導(dǎo)出。不同的導(dǎo)出方式對(duì)應(yīng)不同的導(dǎo)入方式。

在 ES6 模塊中,無(wú)論是否聲明 "use strict;" 語(yǔ)句,默認(rèn)情況下模塊都是在嚴(yán)格模式下運(yùn)行。export 語(yǔ)句不能用在嵌入式腳本中。

命名導(dǎo)出

通過(guò)在聲明的前面加上 export 關(guān)鍵字,一個(gè)模塊可以導(dǎo)出多個(gè)內(nèi)容。這些導(dǎo)出的內(nèi)容通過(guò)名字區(qū)分,被稱為命名導(dǎo)出。

 

  1. // 導(dǎo)出單個(gè)特性(可以導(dǎo)出 var,let,const) 
  2. export let name = "小明"
  3. export function sayHi(name) { 
  4.     console.log(`Hello, ${name}!`); 
  5. export class Sample { 
  6.     ... 

 

或者導(dǎo)出事先定義的特性

 

  1. let name = "小明"
  2. const age = 18; 
  3. function sayHi(name) { 
  4.     console.log(`Hello, ${name}!`); 
  5. export {name, age, sayHi} 

 

導(dǎo)出時(shí)也可以指定別名,別名必須在 export 子句的大括號(hào)語(yǔ)法中指定。因此,聲明值、導(dǎo)出值和未導(dǎo)出值提供別名不能在一行完成。

 

  1. export {name as username, age, sayHi} 

但導(dǎo)出語(yǔ)句必須在模塊頂級(jí),不能嵌套在某個(gè)塊中:

 

  1. // 允許 
  2. export ... 
  3. // 不允許 
  4. if (condition) { 
  5.     export ... 

 

默認(rèn)導(dǎo)出

默認(rèn)導(dǎo)出就好像模塊與被導(dǎo)出的值是一回事。默認(rèn)導(dǎo)出使用 default 關(guān)鍵字將一個(gè)值聲明為默認(rèn)導(dǎo)出,每個(gè)模塊只能有一個(gè)默認(rèn)導(dǎo)出。重復(fù)的默認(rèn)導(dǎo)出會(huì)導(dǎo)致 SyntaxError。如下所示:

 

  1. // 導(dǎo)出事先定義的特性作為默認(rèn)值 
  2. export default { 
  3.     name"Xiao Ming"
  4.     age: 18, 
  5.     sex: "boy" 
  6. }; 
  7. export {sayHi as default}    // ES 6 模塊會(huì)識(shí)別作為別名提供的 default 關(guān)鍵字。此時(shí),雖然對(duì)應(yīng)的值是使用命名語(yǔ)法導(dǎo)出的,實(shí)際上則會(huì)稱為默認(rèn)導(dǎo)出 等同于 export default function sayHi() {} 
  8. // 導(dǎo)出單個(gè)特性作為默認(rèn)值 
  9. export default function () {...} 
  10. export default class {...} 

ES6 規(guī)范對(duì)不同形式的 export 語(yǔ)句中可以使用什么不可以使用什么規(guī)定了限制。某些形式允許聲明和賦值,某些形式只允許表達(dá)式,而某些形式則只允許簡(jiǎn)單標(biāo)識(shí)符。注意,有的形式使用了分號(hào),有的則沒(méi)有。

下面列出幾種會(huì)導(dǎo)致錯(cuò)誤的 export 形式:

  1. // 會(huì)導(dǎo)致錯(cuò)誤的不同形式: 
  2. // 行內(nèi)默認(rèn)導(dǎo)出中不能出現(xiàn)變量聲明 
  3. export default const name = '小劉'
  4. // 只有標(biāo)識(shí)符可以出現(xiàn)在export 子句中 
  5. export { 123 as name } 
  6. // 別名只能在export 子句中出現(xiàn) 
  7. export const name = '小紅' as uname; 

 

注意:聲明、賦值和導(dǎo)出標(biāo)識(shí)符最好分開(kāi)。這樣不容易搞錯(cuò)了,同時(shí)也可以讓 export 語(yǔ)句集中在一塊。而且,沒(méi)有被 export 關(guān)鍵字導(dǎo)出的變量、函數(shù)或類會(huì)在模塊內(nèi)保持私有。

模塊重定向

模塊導(dǎo)入的值還可以再次導(dǎo)出,這樣的話,可以在父模塊集中多個(gè)模塊的多個(gè)導(dǎo)出??梢允褂?export from 語(yǔ)法實(shí)現(xiàn):

 

  1. export {default as m1, namefrom './module1.js' 
  2. // 等效于 
  3. import {default as m1, namefrom "./module1.js" 
  4. export {m1, name

 

外部模塊的默認(rèn)導(dǎo)出也可以重用為當(dāng)前模塊的默認(rèn)導(dǎo)出:

 

  1. export { default } from './module1.js'

也可以在重新導(dǎo)出時(shí),將導(dǎo)入模塊修改為默認(rèn)導(dǎo)出,如下所示:

 

  1. export { name as default } from './module1.js'

而想要將所有命名導(dǎo)出可以使用如下語(yǔ)法:

 

  1. export * from './module1.js'

該語(yǔ)法會(huì)忽略默認(rèn)導(dǎo)出。但這種語(yǔ)法也要注意導(dǎo)出名稱是否沖突。如下所示:

 

  1. // module1.js 
  2. export const name = "module1:name"
  3. // module2.js 
  4. export * from './mudule1.js' 
  5. export const name = "module2:name"
  6. // index.js 
  7. import { name } from './module2.js'
  8. console.log(name); // module2:name 

 

最終輸出的是 module2.js 中的值,這個(gè) “重寫(xiě)” 是靜默發(fā)生的。

導(dǎo)入

使用 export 關(guān)鍵字定義了模塊的對(duì)外接口以后,其它模塊就能通過(guò) import 關(guān)鍵字加載這個(gè)模塊了。但與 export 類似,import 也必須出現(xiàn)在模塊的頂級(jí):

 

  1. // 允許 
  2. import ... 
  3. // 不允許 
  4. if (condition) { 
  5.     import ... 

 

模塊標(biāo)識(shí)符可以是相對(duì)于當(dāng)前模塊的相對(duì)路徑,也可以是指向模塊文件的絕對(duì)路徑。它必須是純字符串,不能是動(dòng)態(tài)計(jì)算的結(jié)果。例如,不能是拼接的字符串。

當(dāng)使用 export 命名導(dǎo)出時(shí),可以使用 * 批量獲取并賦值給保存導(dǎo)出集合的別名,而無(wú)須列出每個(gè)標(biāo)識(shí)符:

 

  1. const name = "Xiao Ming", age = 18, sex = "boy"
  2. export {name, age, sex} 
  3.  
  4. // 上面的命名導(dǎo)出可以使用如下形式導(dǎo)入(上面的代碼是在 module1.js 模塊中) 
  5. import * as Sample from "./module1.js" 
  6. console.log(`My name is ${Sample.name}, A ${Sample.sex},${Sample.age} years old.`); 

 

也可以指名導(dǎo)入,只需要把名字放在 {} 中即可:

 

  1. import {name, sex as s, age} from "./module1.js"
  2. console.log(`My name is ${name}, A ${s},${age} years old.`); 

 

import 引入是采用的 Singleton 模式,多次使用 import 引入同一個(gè)模塊時(shí),只會(huì)引入一次該模塊的實(shí)例:

 

  1. import {name, age} from "./module1.js"
  2. import {sex as s} from "./module1.js"
  3. // 等同于,并且只會(huì)引入一個(gè) module1.js 實(shí)例 
  4. import {name, sex as s, age} from "./module1.js"

 

而使用默認(rèn)導(dǎo)出的話,可以使用 default 關(guān)鍵字并提供別名來(lái)導(dǎo)入,也可以直接使用標(biāo)識(shí)符就是默認(rèn)導(dǎo)出的別名導(dǎo)入:

 

  1. import {default as Sample} from "./module1.js" 
  2. // 與下面的方式等效 
  3. import Sample from "./module1.js" 

 

而模塊中同時(shí)有命名導(dǎo)出和默認(rèn)導(dǎo)出,可以在 import 語(yǔ)句中同時(shí)導(dǎo)入。下面三種方式都等效。

 

  1. import Sample, {sayHi} from "./module1.js" 
  2. import {default as Sample, sayHi} from "./module1.js" 
  3. import Sample, * as M1 from "./module1.js" 

 

當(dāng)然,也可以將整個(gè)模塊作為副作用而導(dǎo)入,而不導(dǎo)入模塊中的特定內(nèi)容。這將運(yùn)行模塊中的全局代碼,但實(shí)際上不導(dǎo)入任何值。

 

  1. import './module1.js' 

import 導(dǎo)入的值與 export 導(dǎo)出的值是綁定關(guān)系,綁定是不可變的。因此,import 對(duì)所導(dǎo)入的模塊是只讀的。但是可以通過(guò)調(diào)用被導(dǎo)入模塊的函數(shù)來(lái)達(dá)到目的。

 

  1. import Sample, * as M1 from "./module1.js" 
  2. Sample = "Modify Sample";    // 錯(cuò)誤 
  3. M1.module1 = "Module 1";    // 錯(cuò)誤 
  4. Sample.name = "小亮";       // 允許 

 

這樣做的好處是能夠支持循環(huán)依賴,并且一個(gè)大的模塊可以拆成若干個(gè)小模塊時(shí)也可以運(yùn)行,只要不嘗試修改導(dǎo)入的值。

注意:如果要在瀏覽器中原生加載模塊,則文件必須帶有 .js 擴(kuò)展名,不然可能無(wú)法解析。而使用構(gòu)建工具或第三方模塊加載器打包或解析 ES6 模塊,可能不需要包含擴(kuò)展名。

import()

標(biāo)準(zhǔn)的 import 關(guān)鍵字導(dǎo)入模塊是靜態(tài)的,會(huì)使所有被導(dǎo)入的模塊,在加載時(shí)就被編譯。而最新的 ES11 標(biāo)準(zhǔn)中引入了動(dòng)態(tài)導(dǎo)入函數(shù) import(),不必預(yù)先加載所有模塊。該函數(shù)會(huì)將模塊的路徑作為參數(shù),并返回一個(gè) Promise,在它的 then 回調(diào)里使用加載后的模塊:

 

  1. import ('./module1.mjs'
  2.     .then((module) => { 
  3.         // Do something with the module. 
  4.     }); 

 

這種使用方式也支持 await 關(guān)鍵字。

 

  1. let module = await import('./module1.js'); 

import() 的使用場(chǎng)景如下:

  • 按需加載。
  • 動(dòng)態(tài)構(gòu)建模塊路徑。
  • 條件加載。

加載

ES6 模塊既可以通過(guò)瀏覽器原生加載,也可以與第三方加載器和構(gòu)建工具一起加載。

完全支持 ES6 模塊的瀏覽器可以從頂級(jí)模塊異步加載整個(gè)依賴圖。瀏覽器會(huì)解析入口模塊,確定依賴,并發(fā)送對(duì)依賴模塊的請(qǐng)求。這些文件通過(guò)網(wǎng)絡(luò)返回后,瀏覽器會(huì)解析它們的內(nèi)容,確認(rèn)依賴,如果二級(jí)依賴還沒(méi)有加載,則會(huì)發(fā)送更多請(qǐng)求。這個(gè)異步遞歸加載過(guò)程會(huì)持續(xù)到整個(gè)依賴圖都解析完成。解析完依賴,應(yīng)用就可以正式加載模塊了。

模塊文件按需加載,且后續(xù)模塊的請(qǐng)求會(huì)因?yàn)槊總€(gè)依賴模塊的網(wǎng)絡(luò)延遲而同步延遲。即,module1 依賴 module2,module2 依賴 module3。瀏覽器在對(duì) module2 的請(qǐng)求完成之前并不知道要請(qǐng)求 module3。這種架子啊方式效率高,也不需要外部工具,但加載大型應(yīng)用的深度依賴圖可能要花費(fèi)很長(zhǎng)時(shí)間。

HTML

想要在 HTML 頁(yè)面中使用 ES6 模塊,需要將 type="module" 屬性放在 <script> 標(biāo)簽中,來(lái)聲明該 <script> 所包含的代碼在瀏覽器中作為模塊執(zhí)行。它可以嵌入在網(wǎng)頁(yè)中,也可以作為外部文件引入:

 

  1. <script type="module"
  2.     // 模塊代碼 
  3. </script> 
  4. <script type="module" src="./module1.js"></script> 

 

 

<script type="module">模塊加載的順序與 <script defer> 加載的腳本一樣按順序執(zhí)行。但執(zhí)行會(huì)延遲到文檔解析完成,但執(zhí)行順序就是<script type="module">在頁(yè)面中出現(xiàn)的順序。

也可以給模塊標(biāo)簽添加 async 屬性。這樣影響是雙重的,不僅模塊執(zhí)行順序不再與 <script> 標(biāo)簽在頁(yè)面中的順序綁定,模塊也不會(huì)等待文檔完成解析才執(zhí)行。不過(guò),入口模塊必須等待其依賴加載完成。

Worker

 

Worker 為了支持 ES6 模塊,在 Worker 構(gòu)造函數(shù)中可以接收第二個(gè)參數(shù),其 type 屬性的默認(rèn)值是 classic,可以將 type 設(shè)置為 module 來(lái)加載模塊文件。如下所示:

  1. // 第二個(gè)參數(shù)默認(rèn)為{ type: 'classic' } 
  2. const scriptWorker = new Worker('scriptWorker.js'); 
  3. const moduleWorker = new Worker('moduleWorker.js', { type: 'module' }); 

在基于模塊的工作者內(nèi)部,self.importScripts() 方法通常用于在基于腳本的工作者中加載外部腳本,調(diào)用它會(huì)拋出錯(cuò)誤。這是因?yàn)槟K的 import 行為包含了 importScripts()。

向后兼容

如果瀏覽器原生支持 ES6 模塊,可以直接使用,而不支持的瀏覽器可以使用第三方模塊系統(tǒng)(System.js)或在構(gòu)建時(shí)將 ES6 模塊進(jìn)行轉(zhuǎn)譯。

 

腳本模塊可以使用 type="module" 屬性設(shè)定,而對(duì)于不支持模塊的瀏覽器,可以使用 nomodule 屬性。此屬性會(huì)通知支持 ES6 模塊的瀏覽器不執(zhí)行腳本。不支持模塊的瀏覽器無(wú)法識(shí)別該屬性,從而忽略該屬性。如下所示:

  1. // 支持模塊的瀏覽器會(huì)執(zhí)行這段腳本 
  2. // 不支持模塊的瀏覽器不會(huì)執(zhí)行這段腳本 
  3. <script type="module" src="module.js"></script> 
  4. // 支持模塊的瀏覽器不會(huì)執(zhí)行這段腳本 
  5. // 不支持模塊的瀏覽器會(huì)執(zhí)行這段腳本 
  6. <script nomodule src="script.js"></script> 

總結(jié)

ES6 在語(yǔ)言層面上支持了模塊,結(jié)束了 CommonJS 和 AMD 這兩個(gè)模塊加載器的長(zhǎng)期分裂狀況,重新定義了模塊功能,集兩個(gè)規(guī)范于一身,并通過(guò)簡(jiǎn)單的語(yǔ)法聲明來(lái)暴露。

模塊的使用不同方式加載 .js 文件,它與腳本有很大的不同:

  1. 模塊始終使用 use strict 執(zhí)行嚴(yán)格模式。
  2. 在模塊的頂級(jí)作用域創(chuàng)建的變量,不會(huì)被自動(dòng)添加到共享的全局作用域,它們只會(huì)在模塊頂級(jí)作用域的內(nèi)部存在。
  3. 模塊頂級(jí)作用域的 this 值為 undefined。
  4. 模塊不允許在代碼中使用 HTML 風(fēng)格的注釋。
  5. 對(duì)于需要讓模塊外部代碼訪問(wèn)的內(nèi)容,模塊必須導(dǎo)出它們。
  6. 允許模塊從其他模塊導(dǎo)入綁定。
  7. 模塊代碼執(zhí)行一次。導(dǎo)出僅創(chuàng)建一次,然后會(huì)在導(dǎo)入之間共享。

 

瀏覽器對(duì)原生模塊的支持越來(lái)越好,但也提供了穩(wěn)健的工具以實(shí)現(xiàn)從不支持到支持 ES6 模塊的過(guò)渡。

 

責(zé)任編輯:華軒 來(lái)源: 今日頭條
相關(guān)推薦

2020-07-09 08:01:48

JavaScriptES模塊

2015-08-26 16:17:49

OpenStack OpenStack 架開(kāi)源云平臺(tái)

2012-07-25 13:25:11

ibmdw

2016-12-30 13:43:35

異步編程RxJava

2009-11-17 09:47:54

Oracle SQL語(yǔ)

2009-11-13 15:55:52

Oracle哈希連接

2022-08-04 18:50:12

Navigator瀏覽器設(shè)備

2023-11-06 09:24:14

CSS相對(duì)顏色

2019-11-06 09:52:01

JavaScript單線程非阻塞

2011-05-25 11:25:23

快速排序Javascript

2017-01-20 08:30:19

JavaScriptfor循環(huán)

2012-02-06 13:52:33

JavaScript

2020-12-25 10:28:41

JavaScript模塊module

2010-09-13 09:18:22

JavaScript模塊模式

2019-12-02 16:05:10

前端模塊化JavaScript

2010-01-12 10:23:52

路由最佳路徑

2024-01-09 07:42:46

Shutil 模塊Python 編程工具

2009-11-12 10:05:09

Visual C++

2017-10-26 08:53:38

前端JavaScript函數(shù)式編程

2016-09-13 21:30:11

JavascriptCSPWeb
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)