摘要:是一個(gè)字符串,它表示模塊的標(biāo)識(shí)。模塊標(biāo)準(zhǔn)主要有兩部分聲明語法和可編程的加載配置模塊如何以及有條件地加載模塊模塊的基礎(chǔ)在的模塊系統(tǒng)中,有兩種命名的和默認(rèn)的。
在這幾天的工作中,我需要調(diào)用同事編寫的兼容jQuery和React的通用組件。他為了兼容jQuery風(fēng)格的調(diào)用和React的組件化,分別export了一個(gè)default和幾個(gè)方法函數(shù)。在調(diào)用的過程中,出現(xiàn)了一些小插曲:React代碼和老的jQuery老代碼調(diào)用時(shí)應(yīng)該怎么正確的import?雖然是很低級(jí)的問題,但是引發(fā)了我一些思考:export 和 import 與 module.exports 和 exports 之間的關(guān)系以及JavaScript模塊系統(tǒng)的發(fā)展歷程。
JavScript這門語言,在設(shè)計(jì)之初是沒有自己的模塊系統(tǒng)的。但是在 ES6 正式發(fā)布之前,社區(qū)已經(jīng)中已經(jīng)出現(xiàn)了一些庫,實(shí)現(xiàn)了簡單的模塊風(fēng)格,并且這種風(fēng)格在 ES6 中也是適用的:
每個(gè)模塊都是一段代碼,加載之后只會(huì)解析過程只會(huì)執(zhí)行一次;
在模塊中可以聲明變量,函數(shù),類等;
默認(rèn)情況下,這些聲明都是這個(gè)模塊的局部聲明;
可以將一些聲明導(dǎo)出,讓其他模塊引用;
一個(gè)模塊可以通過模塊標(biāo)識(shí)符或者文件路徑引入其他模塊;
模塊都是單例的,即使多次引用,也只有一個(gè)實(shí)例;
有一點(diǎn)要注意,避免通過global作為來引用自己的模塊,因?yàn)?b>global本身也是一個(gè)模塊。
ES5中的模塊系統(tǒng)前面說到的,在 ES6 之前,JavaScript 是沒有模塊系統(tǒng)這一說的。在社區(qū)的模塊風(fēng)格出現(xiàn)之前,編寫 JavaScript常常會(huì)遇到這種情況:
所有的代碼寫在一個(gè)文件里面,按照依賴順序,被依賴的方法必須寫在前面。 簡單粗暴,但是問題很多
通用的代碼無法重復(fù)利用。
單個(gè)文件會(huì)越來越大,后期的命名也會(huì)越來越艱難。
按照功能將代碼拆分成不同文件,按照依賴順序加載,被依賴的方法必須先加載。通用代碼可以復(fù)用,但是問題還是很多
過多全局變量,容易沖突。
過多 JavaScript 腳本加載導(dǎo)致頁面阻塞(雖然 HTML5中的 defer和 async可以適當(dāng)?shù)臏p輕這個(gè)問題)。
過多依賴不方便管理和開發(fā)。
隨著 JavaScript 的地位慢慢提高,為了滿足日常開發(fā)的需要,社區(qū)中慢慢出現(xiàn)了相對比較同意的模塊標(biāo)準(zhǔn),主要有兩種:
CommonJS Modules: 這個(gè)標(biāo)準(zhǔn)主要在 Node.js 中實(shí)現(xiàn)(Node.js的模塊比 CommonJS 好稍微多一些特性)。其特點(diǎn)是:
簡單的語法
為同步加載和服務(wù)端而設(shè)計(jì)
Asynchronous Module Definition (AMD): 這個(gè)標(biāo)準(zhǔn)最受歡迎的實(shí)現(xiàn)實(shí)在 RequireJS 中。其特點(diǎn)是:
稍微復(fù)雜一點(diǎn)點(diǎn)的語法,使得AMD的運(yùn)行不需要編譯
為異步加載和瀏覽器而設(shè)計(jì)
上述只是 ES5 模塊系統(tǒng)的簡單介紹,如果有興趣可以去看看Writing Modular JavaScript With AMD, CommonJS & ES Harmony。
CommonJS Modules 在 Node.js 中的實(shí)現(xiàn)根據(jù)CommonJS規(guī)范,一個(gè)多帶帶的文件就是一個(gè)模塊。每一個(gè)模塊都是一個(gè)多帶帶的作用域,在該模塊內(nèi)部定義的變量,無法被其他模塊讀取,除非定義為global對象的屬性,或者將屬性暴露出來。在 Nodejs就是如此。
比如:
const circle = require("./circle.js"); // 使用 require 加載模塊 circle console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);
在 circle.js 中:
const PI = Math.PI; exports.area = (r) => PI * r * r; exports.circumference = (r) => 2 * PI * r;
circle.js 模塊導(dǎo)出了 area()和 circumffference()兩個(gè)方法,變量 PI是這個(gè)模塊的私有變量。如果想為自定義的模塊添加屬性或者方法,將它們添加到 exports 這個(gè)特殊的對象上就可以達(dá)到目的。
如果希望模塊提供的接口是一個(gè)構(gòu)造函數(shù),或者輸出的是一個(gè)完整的對象而不是一個(gè)屬性,那么可以使用 module.exports 代替 exports。但是注意,exports 是 module.exports 的一個(gè)引用,只是為了用起來方便,只要沒有重寫 module.exports對象,那么module.exports.xxx就等價(jià)于exports.xxx。
const square = require("./square.js"); var mySquare = square(2); console.log(`The area of my square is ${mySquare.area()}`);
square.js:
module.exports = (width) => { return { area: () => width * width }; }AMD規(guī)范
AMD是“Asynchronous Module Definition”的縮寫。通過異步方式加載模塊,模塊的加載不影響后續(xù)語句的執(zhí)行,所有依賴加載中的模塊的語句,都會(huì)放在一個(gè)回調(diào)函數(shù)中,等到該模塊加載完成后,這個(gè)回調(diào)函數(shù)才運(yùn)行。注意,在 AMD 中模塊名是全局作用域,可以在全局引用。
AMD規(guī)范的API非常簡單:
define(id?, dependencies?, factory);
規(guī)范定義了一個(gè)define函數(shù),它用來定義一個(gè)模塊。它包含三個(gè)參數(shù),前兩個(gè)參數(shù)都是可選的。
id:是一個(gè)string字符串,它表示模塊的標(biāo)識(shí)。通常用來定義這個(gè)模塊的名字,一般不用
dependencies:是一個(gè)數(shù)組,依賴的模塊的標(biāo)識(shí)。也是可選的參數(shù),每個(gè)依賴的模塊的輸出將作為參數(shù)一次傳入 factory 中。如果沒有指定 dependencies,那么它的默認(rèn)值是 ["require", "exports", "module"]。
factory:一個(gè)函數(shù)或者對象。如果是函數(shù),在依賴的模塊加載成功后,會(huì)執(zhí)行這個(gè)回調(diào)函數(shù),它的返回值就是模塊的輸出接口或值。它的參數(shù)是所有依賴模塊的引用。
定義一個(gè)名為 myModule 的模塊,它依賴 jQuery 模塊:
define("myModule", ["jquery"], function($) { // $ 是 jquery 模塊的輸出 $("body").text("hello world"); }); // 使用 define(["myModule"], function(myModule) {});ES6中的模塊系統(tǒng)
ES6 模塊系統(tǒng)的目標(biāo)就是創(chuàng)建一個(gè)統(tǒng)一的模塊格式,讓 CommonJS 和 AMD的使用者都滿意:
和CommonJS類似,但是更加簡潔的語法,循環(huán)引用的支持更好。
和AMD類似,直接支持異步加載和可配置的模塊加載。
模塊標(biāo)準(zhǔn)主要有兩部分:
聲明語法:import 和 export
可編程的加載 API:配置模塊如何以及有條件地加載模塊
ES6模塊的基礎(chǔ)在 ES6的模塊系統(tǒng)中,有兩種 export:命名的 export 和默認(rèn)的 export。在一個(gè)文件中,命名的 export 可以有多個(gè),而默認(rèn)的 default export 只能有一個(gè)。可以同時(shí)使用,但最好還是分開使用。
也可以在聲明表達(dá)式前面加上 export 關(guān)鍵字可以直接導(dǎo)出將聲明的對象導(dǎo)出:
//------ lib.js ------ export const sqrt = Math.sqrt; export function square(x) { return x * x; } export function diag(x, y) { return sqrt(square(x) + square(y)); } //------ main.js ------ import { square, diag } from "lib"; console.log(square(11)); // 121 console.log(diag(4, 3)); // 5
如果要導(dǎo)出一個(gè)已經(jīng)存在的變量,需要加上{}:
const random = Math.random; export random; // SyntaxError: Unexpected token, expected { export { random };
使用 CommonJS 語法實(shí)現(xiàn)相同目的:
//------ lib.js ------ var sqrt = Math.sqrt; function square(x) { return x * x; } function diag(x, y) { return sqrt(square(x) + square(y)); } module.exports = { sqrt: sqrt, square: square, diag: diag, }; //------ main.js ------ var square = require("lib").square; var diag = require("lib").diag; console.log(square(11)); // 121 console.log(diag(4, 3)); // 5
下面是來自 MDN 的更加完整的export 語法:
export { name1, name2, …, nameN }; export { variable1 as name1, variable2 as name2, …, nameN }; export let name1, name2, …, nameN; // also var export let name1 = …, name2 = …, …, nameN; // also var, const export expression; export default expression; export default function (…) { … } // also class, function* export default function name1(…) { … } // also class, function* export { name1 as default, … }; export * from …; export { name1, name2, …, nameN } from …; export { import1 as name1, import2 as name2, …, nameN } from …;
每個(gè)模塊只有一個(gè)默認(rèn)導(dǎo)出的值, default export 可以是一個(gè)函數(shù),一個(gè)類,一個(gè)對象或者其他任意值。有兩種形式的 default export:
被標(biāo)記的聲明。導(dǎo)出一個(gè)函數(shù)或者類
直接導(dǎo)出值。導(dǎo)出表達(dá)式的運(yùn)行結(jié)果
導(dǎo)出一個(gè)函數(shù):
//------ myFunc.js ------ export default function () {} // 沒有分號(hào) 函數(shù)名可有可無 //------ main1.js ------ import myFunc from "myFunc"; myFunc();
導(dǎo)出一個(gè)類:
//------ MyClass.js ------ export default class {} // 沒有分號(hào) 類名可有可無 //------ main2.js ------ import MyClass from "MyClass"; const inst = new MyClass();
導(dǎo)出表達(dá)式運(yùn)行結(jié)果:
export default "abc"; export default foo(); export default /^xyz$/; export default 5 * 7; export default { no: false, yes: true };
前面說的到的導(dǎo)出匿名函數(shù)和類,可以將其視為導(dǎo)出表達(dá)式的運(yùn)行結(jié)果:
export default (function () {}); export default (class {});
每一個(gè) default export 都是這種結(jié)構(gòu):
export default <>
相當(dāng)于:
const __default__ = <>; export { __default__ as default }; // (A)
export后面是不能接變量聲明的,因?yàn)橐粋€(gè)變量聲明表達(dá)式中可以一次生命多個(gè)變量。考慮下面這種情況:
export default const foo = 1, bar = 2, baz = 3; // not legal JavaScript!
應(yīng)該導(dǎo)出 foo,bar,還是 baz 呢?
if (Math.random()) { import "foo"; // SyntaxError } // You can’t even nest `import` and `export` // inside a simple block: { import "foo"; // SyntaxError }
模塊的 import 會(huì)被提升到當(dāng)前作用域的頂部。所以下面這種情況是可行的:
foo(); import { foo } from "my_module";
import的基本語法:
import defaultMember from "module-name"; import * as name from "module-name"; import { member } from "module-name"; import { member as alias } from "module-name"; import { member1 , member2 } from "module-name"; import { member1 , member2 as alias2 , [...] } from "module-name"; import defaultMember, { member [ , [...] ] } from "module-name"; import defaultMember, * as name from "module-name"; import "module-name";對循環(huán)引用的支持
什么是循環(huán)引用?模塊A 引用了模塊 B,模塊 B 又引用了模塊 A。如果可能的話,應(yīng)該避免這種情況出現(xiàn),這會(huì)使得模塊之間過度的耦合。但是這種有時(shí)候又是無法避免的。
CommonJS 中的循環(huán)引用a.js 中的內(nèi)容:
console.log("模塊 a 開始了!"); exports.done = false; var b = require("./b.js"); console.log("在 a 中, b.done = %j", b.done); exports.done = true; console.log("模塊 a 結(jié)束了!");
b.js 中的內(nèi)容:
console.log("模塊 b 開始了!"); exports.done = false; var a = require("./a.js"); console.log("在 b 中, a.done = %j", a.done); exports.done = true; console.log("模塊 b 結(jié)束了!");
main.js 中的內(nèi)容:
console.log("main 開始了!"); var a = require("./a.js"); var b = require("./b.js"); console.log("在 main 中, a.done=%j, b.done=%j", a.done, b.done);
當(dāng) main.js 加載 a.js 時(shí),a.js 又加載 b.js。這個(gè)時(shí)候,b.js 又會(huì)嘗試去加載 a.js 。為了防止出現(xiàn)無限循環(huán)的加載,a.js 中的 exports 對象會(huì)返回一個(gè) unfinished copy 給 b.js 模塊。然后模塊 b 完成加載,同時(shí)將提供模塊 a 的接口。當(dāng) main.js 加載完 a,b 兩個(gè)模塊之后,輸出如下:
main 開始了! 模塊 a 開始了! 模塊 b 開始了! 在 b 中, a.done = false 模塊 b 結(jié)束了! 在 a 中, b.done = true 模塊 a 結(jié)束了! 在 main 中, a.done=true, b.done=true
這種方式有其局限性:
Nodejs風(fēng)格的單個(gè)值的導(dǎo)出無法工作。當(dāng)a使用 module.exports 導(dǎo)出一個(gè)值時(shí),那么 b 模塊中引用的變量 a 在聲明之后就不會(huì)再更新
module.exports = function(){};
無法直接命名你的引用
var foo = require("a").foo; // foo is undefinedES6中的循環(huán)引用
ES6中,imports 是 exprts 的只讀視圖,直白一點(diǎn)就是,imports 都指向 exports 原本的數(shù)據(jù),比如:
//------ lib.js ------ export let counter = 3; export function incCounter() { counter++; } //------ main.js ------ import { counter, incCounter } from "./lib"; // The imported value `counter` is live console.log(counter); // 3 incCounter(); console.log(counter); // 4 // The imported value can’t be changed counter++; // TypeError
因此在 ES6中處理循環(huán)引用特別簡單,看下面這段代碼:
//------ a.js ------ import {bar} from "b"; // (i) export function foo() { bar(); // (ii) } //------ b.js ------ import {foo} from "a"; // (iii) export function bar() { if (Math.random()) { foo(); // (iv) } }
假設(shè)先加載模塊 a,在模塊 a 加載完成之后,bar 間接性地指向的是模塊 b 中的 bar。無論是加載命令的 imports 還是未完成的 imports,imports 和 exports 之間都有一個(gè)間接的聯(lián)系,所以總是可以正常工作。
ES6 模塊加載器 API除了聲明式加載模塊,ES6還提供了一個(gè)可編程的 API:
以編程的方式使用模塊
配置模塊的加載
要注意,這個(gè) API 并不是ES6標(biāo)準(zhǔn)中的一部分,在“JavaScript Loader Standrad”中,并且具體的標(biāo)準(zhǔn)還在制定中,所以下面講到的內(nèi)容都是試驗(yàn)性的。
Loaders 的簡單使用Loader 用于處理模塊標(biāo)識(shí)符和加載模塊等。它的 construct 是Reflect.Loader。每個(gè)平臺(tái)在全局作用域中都有一個(gè)全局變量System的實(shí)例來實(shí)現(xiàn) loader 的一些特性。
你可以通過 API 提供的 Promise,以編碼的方式 import 一個(gè)模塊:
System.import("some_module") .then(some_module => { // Use some_module }) .catch(error => { ··· });
System.import() 可以:
可以在 script 標(biāo)簽中使用模塊
有條件地加載模塊
System.import() 返回一個(gè)模塊, 可以用 Promise.all() 來導(dǎo)入多個(gè)模塊:
Promise.all( ["module1", "module2", "module3"] .map(x => System.import(x))) .then(([module1, module2, module3]) => { // Use module1, module2, module3 });
Loader 還有一些其他方法,最重要的三個(gè)是:
System.module(source, [options])
將 source 中的 JavaScript 代碼當(dāng)做一個(gè)模塊執(zhí)行,返回一個(gè) Promise
System.set(name, modules)
注冊一個(gè)模塊,比如用 System.module 創(chuàng)建的模塊
System.define(name, source, [options])
執(zhí)行 source 中的代碼,將返回的結(jié)果注冊為一個(gè)模塊
目前 Loader API 還處于試驗(yàn)階段,更多的細(xì)節(jié)不想在深入。有興趣的話可以去看看
模塊導(dǎo)入的細(xì)節(jié)在 CommonJS 和 ES6中,兩種模塊導(dǎo)入方式有一些不同:
在 CommonJS 中,導(dǎo)入的內(nèi)容是模塊導(dǎo)出的內(nèi)容的拷貝。
在 ES6 中,導(dǎo)出值得實(shí)時(shí)只讀視圖,類似于引用。
在 CommonJS 中,如果你將一個(gè)導(dǎo)入的值保存到一個(gè)變量中,這個(gè)值會(huì)被復(fù)制兩次:第一次是這個(gè)值所屬模塊導(dǎo)出時(shí)(行 A),第二次是這個(gè)值被引用時(shí)(行 B)。
//------ lib.js ------ var counter = 3; function incCounter() { counter++; } module.exports = { counter: counter, // (A) incCounter: incCounter, }; //------ main1.js ------ var counter = require("./lib").counter; // (B) var incCounter = require("./lib").incCounter; // The imported value is a (disconnected) copy of a copy console.log(counter); // 3 incCounter(); console.log(counter); // 3 // The imported value can be changed counter++; console.log(counter); // 4
如果通過 exports對象來訪問這個(gè)值,這個(gè)值還是會(huì)再復(fù)制一次:
//------ main2.js ------ var lib = require("./lib"); // The imported value is a (disconnected) copy console.log(lib.counter); // 3 lib.incCounter(); console.log(lib.counter); // 3 // The imported value can be changed lib.counter++; console.log(lib.counter); // 4
和 CommonJS 不同的是,在 ES6中,所有的導(dǎo)入的數(shù)據(jù)都是導(dǎo)出值的視圖,每一個(gè)導(dǎo)入的數(shù)據(jù)都和原始的數(shù)據(jù)有一個(gè)實(shí)時(shí)連接(并不是 JS 中Object引用的那種概念,因?yàn)閷?dǎo)出的值可以是一個(gè)原始類型,primitive type,而且導(dǎo)入的數(shù)據(jù)是只讀的)。
無條件的引入 (import x from "foo") 就是用 const 聲明的變量
模塊的屬性foo (import * as foo from "foo") 則是創(chuàng)建一個(gè) frozen object.
//------ lib.js ------ export let counter = 3; export function incCounter() { counter++; } //------ main1.js ------ import { counter, incCounter } from "./lib"; // The imported value `counter` is live console.log(counter); // 3 incCounter(); console.log(counter); // 4 // The imported value can’t be changed counter++; // TypeError
如果使用*引入模塊,會(huì)得到相同的結(jié)果:
//------ main2.js ------ import * as lib from "./lib"; // 導(dǎo)入的值 counter 是活動(dòng)的 console.log(lib.counter); // 3 lib.incCounter(); console.log(lib.counter); // 4 // 導(dǎo)入的值是只讀的不能被修改 lib.counter++; // TypeError
雖然不能修改導(dǎo)入的值,但是可以修改對象指向的內(nèi)容,這個(gè) const 常量的處理是一致的。例如:
//------ lib.js ------ export let obj = {}; //------ main.js ------ import { obj } from "./lib"; obj.prop = 123; // OK obj = {}; // TypeError結(jié)束
關(guān)于更多 ES6 模塊相關(guān)的內(nèi)容,有興趣的朋友可以去下面這些地方看看:
http://exploringjs.com/es6/ch_modules.html
http://www.ecma-international.org/ecma-262/6.0/#sec-modules
參考資料:
http://stackoverflow.com/a/40295288
http://exploringjs.com/es6/ch_modules.html
http://zhaoda.net/webpack-handbook/amd.html
[https://nodejs.org/api/module...
http://speakingjs.com/es5/ch17.html#freezing_objects
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/81165.html
摘要:系列種優(yōu)化頁面加載速度的方法隨筆分類中個(gè)最重要的技術(shù)點(diǎn)常用整理網(wǎng)頁性能管理詳解離線緩存簡介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問性能優(yōu)化方案實(shí)現(xiàn)的大排序算法一怪對象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁面加載速度的方法 隨筆分類 - HTML5 HTML5中40個(gè)最重要的技術(shù)點(diǎn) 常用meta整理 網(wǎng)頁性能管理詳解 HTML5 ...
摘要:系列種優(yōu)化頁面加載速度的方法隨筆分類中個(gè)最重要的技術(shù)點(diǎn)常用整理網(wǎng)頁性能管理詳解離線緩存簡介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問性能優(yōu)化方案實(shí)現(xiàn)的大排序算法一怪對象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁面加載速度的方法 隨筆分類 - HTML5 HTML5中40個(gè)最重要的技術(shù)點(diǎn) 常用meta整理 網(wǎng)頁性能管理詳解 HTML5 ...
摘要:概述是一款遵循規(guī)范協(xié)議的模塊加載器,不但能在瀏覽器端充分利用,同樣能在其他的運(yùn)行時(shí)環(huán)境,比如和。使用像這樣的模塊加載器能提高代碼的質(zhì)量和開發(fā)速度。一般放在頁面的入口出,用來加載其他的模塊。 RequireJS概述 RequireJS是一款遵循AMD規(guī)范協(xié)議的JavaScript模塊加載器, 不但能在瀏覽器端充分利用,同樣能在其他的JavaScript運(yùn)行時(shí)環(huán)境, 比如Rhino和No...
摘要:理解的函數(shù)基礎(chǔ)要搞好深入淺出原型使用原型模型,雖然這經(jīng)常被當(dāng)作缺點(diǎn)提及,但是只要善于運(yùn)用,其實(shí)基于原型的繼承模型比傳統(tǒng)的類繼承還要強(qiáng)大。中文指南基本操作指南二繼續(xù)熟悉的幾對方法,包括,,。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。 怎樣使用 this 因?yàn)楸救藢儆趥吻岸?,因此文中只看懂?8 成左右,希望能夠給大家?guī)韼椭?...(據(jù)說是阿里的前端妹子寫的) this 的值到底...
閱讀 1637·2021-09-26 09:55
閱讀 1371·2021-09-23 11:22
閱讀 2726·2021-09-06 15:02
閱讀 2640·2021-09-01 11:43
閱讀 3952·2021-08-27 13:10
閱讀 3676·2021-08-12 13:24
閱讀 2069·2019-08-30 12:56
閱讀 2991·2019-08-30 11:22