摘要:的主要思想是異步模塊,主邏輯在回調(diào)函數(shù)中執(zhí)行,這和瀏覽器前端所習(xí)慣的開發(fā)方式不謀而合,應(yīng)運而生。是目前開發(fā)中使用率最高的模塊化標(biāo)準(zhǔn)。
原文鏈接: http://yanjiie.me
偶然的一個周末復(fù)習(xí)了一下 JS 的模塊標(biāo)準(zhǔn),刷新了一下對 JS 模塊化的理解。
從開始 Coding 以來,總會周期性地突發(fā)奇想進(jìn)行 Code Review。既是對一段時期的代碼進(jìn)行總結(jié),也是對那一段時光的懷念。
距離上一次 Review 已經(jīng)過去近兩個月,這次竟然把兩年前在源續(xù)寫的代碼翻了出來,代碼雜亂無章的程度就像那時更加浮躁的自己,讓人感慨時光流逝之快。
話不多說,直接上碼。
當(dāng)時在做的是一個境外電商項目(越南天寶商城),作為非 CS 的新手程序員,接觸 Coding 時間不長和工程化觀念不強(qiáng),在當(dāng)時的項目中出現(xiàn)了這樣的代碼:
import.js:
這段代碼看起來就是不斷地從 DOM 中插進(jìn) CSS 和 JS,雖然寫得很爛,但是很能反映以前的 Web 開發(fā)方式。
在 Web 開發(fā)中,有一個原則叫“關(guān)注點分離(separation of concerns)“,意思是各種技術(shù)只負(fù)責(zé)自己的領(lǐng)域,不互相耦合混合在一起,所以催生出了 HTML、CSS 和 JavaScript。
其中,在 Web 中負(fù)責(zé)邏輯和交互 的 JavaScript,是一門只用 10 天設(shè)計出來的語言,雖然借鑒了許多優(yōu)秀靜態(tài)和動態(tài)語言的優(yōu)點,但卻一直沒有模塊 ( module ) 體系。這導(dǎo)致了它將一個大程序拆分成互相依賴的小文件,再用簡單的方法拼裝起來。其他語言都有這項功能,比如 Ruby 的 require、Python 的 import,甚至就連 CSS 都有 @import,但是 JavaScript 任何這方面的支持都沒有。而且 JS 是一種加載即運行的技術(shù),在頁面中插入腳本時還需要考慮庫的依賴,JS 在這方面的缺陷,對開發(fā)大型的、復(fù)雜的項目形成了巨大障礙。
發(fā)展過程雖然 JS 本身并不支持模塊化,但是這并不能阻擋 JS 走向模塊化的道路。既然本身不支持,那么就從代碼層面解決問題?;钴S的社區(qū)開始制定了一些模塊方案,其中最主要的是 CommonJS 和 AMD,ES6 規(guī)范出臺之后,以一種更簡單的形式制定了 JS 的模塊標(biāo)準(zhǔn) (ES Module),并融合了 CommonJS 和 AMD 的優(yōu)點。
大致的發(fā)展過程:
CommonJS(服務(wù)端) => AMD (瀏覽器端) => CMD / UMD => ES Module
CommonJS 規(guī)范2009年,Node.js 橫空出世,JS 得以脫離瀏覽器運行,我們可以使用 JS 來編寫服務(wù)端的代碼了。對于服務(wù)端的 JS,沒有模塊化簡直是不能忍。
CommonJs (前 ServerJS) 在這個階段應(yīng)運而生,制定了 Module/1.0 規(guī)范,定義了第一版模塊標(biāo)準(zhǔn)。
標(biāo)準(zhǔn)內(nèi)容:
模塊通過變量 exports 來向外暴露 API,exports 只能是一個對象,暴露的 API 須作為此對象的屬性。
定義全局函數(shù) require,通過傳入模塊標(biāo)識來引入其他模塊,執(zhí)行的結(jié)果即為別的模塊暴露出來的 API。
如果被 require 函數(shù)引入的模塊中也包含依賴,那么依次加載這些依賴。
特點:
模塊可以多次加載,首次加載的結(jié)果將會被緩存,想讓模塊重新運行需要清除緩存。
模塊的加載是一項阻塞操作,也就是同步加載。
它的語法看起來是這樣的:
// a.js module.exports = { moduleFunc: function() { return true; }; } // 或 exports.moduleFunc = function() { return true; }; // 在 b.js 中引用 var moduleA = require("a.js"); // 或 var moduleFunc = require("a.js").moduleFunc; console.log(moduleA.moduleFunc()); console.log(moduleFunc())AMD 規(guī)范(Asynchromous Module Definition)
CommonJS 規(guī)范出現(xiàn)后,在 Node 開發(fā)中產(chǎn)生了非常良好的效果,開發(fā)者希望借鑒這個經(jīng)驗來解決瀏覽器端 JS 的模塊化。
但大部分人認(rèn)為瀏覽器和服務(wù)器環(huán)境差別太大,畢竟瀏覽器端 JS 是通過網(wǎng)絡(luò)動態(tài)依次加載的,而不是像服務(wù)端 JS 存在本地磁盤中。因此,瀏覽器需要實現(xiàn)的是異步模塊,模塊在定義的時候就必須先指明它所需要依賴的模塊,然后把本模塊的代碼寫在回調(diào)函數(shù)中去執(zhí)行,最終衍生出了 AMD 規(guī)范。
AMD 的主要思想是異步模塊,主邏輯在回調(diào)函數(shù)中執(zhí)行,這和瀏覽器前端所習(xí)慣的開發(fā)方式不謀而合,RequireJS 應(yīng)運而生。
標(biāo)準(zhǔn)內(nèi)容:
用全局函數(shù) define 來定義模塊,用法為:define(id?, dependencies?, factory);
id 為模塊標(biāo)識,遵從 CommonJS Module Identifiers 規(guī)范
dependencies 為依賴的模塊數(shù)組,在 factory 中需傳入形參與之一一對應(yīng),如果 dependencies 省略不寫,則默認(rèn)為 ["require", "exports", "module"] ,factory 中也會默認(rèn)傳入 require, exports, module,與 ComminJS 中的實現(xiàn)保持一致
如果 factory 為函數(shù),模塊對外暴露 API 的方法有三種:return 任意類型的數(shù)據(jù)、exports.xxx = xxx 或 module.exports = xxx
如果 factory 為對象,則該對象即為模塊的返回值
特點:
前置依賴,異步加載
便于管理模塊之間的依賴性,有利于代碼的編寫和維護(hù)。
它的用法看起來是這樣的:
// a.js define(function (require, exports, module) { console.log("a.js"); exports.name = "Jack"; }); // b.js define(function (require, exports, module) { console.log("b.js"); exports.desc = "Hello World"; }); // main.js require(["a", "b"], function (moduleA, moduleB) { console.log("main.js"); console.log(moduleA.name + ", " + moduleB.desc); }); // 執(zhí)行順序: // a.js // b.js // main.js
人無完人,AMD/RequireJS 也存在飽受詬病的缺點。按照 AMD 的規(guī)范,在定義模塊的時候需要把所有依賴模塊都羅列一遍(前置依賴),而且在使用時還需要在 factory 中作為形參傳進(jìn)去。
define(["a", "b", "c", "d", "e", "f", "g"], function(a, b, c, d, e, f, g){ ..... });
看起來略微不爽 ...
RequireJS 模塊化的順序是這樣的:模塊預(yù)加載 => 全部模塊預(yù)執(zhí)行 => 主邏輯中調(diào)用模塊,所以實質(zhì)是依賴加載完成后還會預(yù)先一一將模塊執(zhí)行一遍,這種方式會使得程序效率有點低。
所以 RequireJS 也提供了就近依賴,會在執(zhí)行至 require 方法才會去進(jìn)行依賴加載和執(zhí)行,但這種方式的用戶體驗不是很好,用戶的操作會有明顯的延遲(下載依賴過程),雖然可以通過各種 loading 去解決。
// 就近依賴 define(function () { setTimeout(function () { require(["a"], function (moduleA) { console.log(moduleA.name); }); }, 1000); });CMD 規(guī)范(Common Module Definition)
AMD/RequireJS 的 JS 模塊實現(xiàn)上有很多不優(yōu)雅的地方,長期以來在開發(fā)者中廣受詬病,原因主要是不能以一種更好的管理模塊的依賴加載和執(zhí)行,雖然有不足的地方,但它提出的思想在當(dāng)時是非常先進(jìn)的。
既然優(yōu)缺點那么必然有人出來完善它,SeaJS 在這個時候出現(xiàn)。
SeaJS 遵循的是 CMD 規(guī)范,CMD 是在 AMD 基礎(chǔ)上改進(jìn)的一種規(guī)范,解決了 AMD 對依賴模塊的執(zhí)行時機(jī)處理問題。
SeaJS 模塊化的順序是這樣的:模塊預(yù)加載 => 主邏輯調(diào)用模塊前才執(zhí)行模塊中的代碼,通過依賴的延遲執(zhí)行,很好解決了 RequireJS 被詬病的缺點。
SeaJS 用法和 AMD 基本相同,并且融合了 CommonJS 的寫法:
// a.js define(function (require, exports, module) { console.log("a.js"); exports.name = "Jack"; }); // main.js define(function (require, exports, module) { console.log("main.js"); var moduleA = require("a"); console.log(moduleA.name); }); // 執(zhí)行順序 // main.js // a.js
除此之外,SeaJS 還提供了 async API,實現(xiàn)依賴的延遲加載。
// main.js define(function (require, exports, module) { var moduleA = require.async("a"); console.log(moduleA.name); });
SeaJS 的出現(xiàn),貌似以一種比較完美的形式解決了 JS 模塊化的問題,是 CommonJS 在瀏覽器端的踐行者,并吸收了 RequestJS 的優(yōu)點。
ES ModuleES Module 是目前 web 開發(fā)中使用率最高的模塊化標(biāo)準(zhǔn)。
隨著 JS 模塊化開發(fā)的呼聲越來越高,作為 JS 語言規(guī)范的官方組織 ECMA 也開始將 JS 模塊化納入 TC39 提案中,并在 ECMAScript 6.0 中得到實踐。
ES Module 吸收了其他方案的優(yōu)點并以更優(yōu)雅的形式實現(xiàn)模塊化,它的思想是盡量的靜態(tài)化,即在編譯時就確定所有模塊的依賴關(guān)系,以及輸入和輸出的變量,和 CommonJS 和 AMD/CMD 這些標(biāo)準(zhǔn)不同的是,它們都是在運行時才能確定需要依賴哪一些模塊并且執(zhí)行它。ES Module 使得靜態(tài)分析成為可能。有了它,就能進(jìn)一步拓寬 JavaScript 的語法,實現(xiàn)一些只能靠靜態(tài)分析實現(xiàn)的功能(比如引入宏(macro)和類型檢驗(type system)。
標(biāo)準(zhǔn)內(nèi)容:
模塊功能主要由兩個命令構(gòu)成:export 和 import。export 命令用于規(guī)定模塊的對外接口,import 命令用于輸入其他模塊提供的功能。
通過 export 命令定義了模塊的對外接口,其他 JS 文件就可以通過 import 命令加載這個模塊。
ES Module 可以有多種用法:
模塊的定義:
/** * export 只支持對象形式導(dǎo)出,不支持值的導(dǎo)出,export default 命令用于指定模塊的默認(rèn)輸出, * 只支持值導(dǎo)出,但是只能指定一個,本質(zhì)上它就是輸出一個叫做 default 的變量或方法 */ // 寫法 1 export var m = 1; // 寫法 2 var m = 1; export { m }; // 寫法 3 var n = 1; export { n as m }; // 寫法 4 var n = 1; export default n;
模塊的引入:
// 解構(gòu)引入 import { firstName, lastName, year } from "a-module"; // 為輸入的變量重新命名 import { lastName as surname } from "a-module"; // 引出模塊對象(引入所有) import * as ModuleA from "a-module";
在使用 ES Module 值得注意的是:import 和 export 命令只能在模塊的頂層,在代碼塊中將會報錯,這是因為 ES Module 需要在編譯時期進(jìn)行模塊靜態(tài)優(yōu)化,import 和 export 命令會被 JavaScript 引擎靜態(tài)分析,先于模塊內(nèi)的其他語句執(zhí)行,這種設(shè)計有利于編譯器提高效率,但也導(dǎo)致無法在運行時加載模塊(動態(tài)加載)。
對于這個缺點,TC39 有了一個新的提案 -- Dynamic Import,提案的內(nèi)容是建議引入 import() 方法,實現(xiàn)模塊動態(tài)加載。
// specifier: 指定所要加載的模塊的位置 import(specifier)
import() 方法返回的是一個 Promise 對象。
import("b-module") .then(module => { module.helloWorld(); }) .catch(err => { console.log(err.message); });
import() 函數(shù)可以用在任何地方,不僅僅是模塊,非模塊的腳本也可以使用。它是運行時執(zhí)行,也就是說,什么時候運行到這一句,就會加載指定的模塊。另外,import() 函數(shù)與所加載的模塊沒有靜態(tài)連接關(guān)系,這點也是與 import 語句不相同。import() 類似于 Node 的 require 方法,區(qū)別主要是前者是異步加載,后者是同步加載。
通過 import 和 export 命令以及 import() 方法,ES Module 幾乎實現(xiàn)了 CommonJS/AMD/CMD 方案的所有功能,更重要的是它是作為 ECMAScript 標(biāo)準(zhǔn)出現(xiàn)的,帶有正統(tǒng)基因,這也是它在現(xiàn)在 Web 開發(fā)中廣泛應(yīng)用的原因之一。
但 ES Module 是在 ECMAScript 6.0 標(biāo)準(zhǔn)中的,而目前絕大多數(shù)的瀏覽器并直接支持 ES6 語法,ES Module 并不能直接使用在瀏覽器上,所以需要 Babel 先進(jìn)行轉(zhuǎn)碼,將 import 和 export 命令轉(zhuǎn)譯成 ES2015 語法才能被瀏覽器解析。
總結(jié)JS 模塊化的出現(xiàn)使得前端工程化程度越來越高,讓使用 JS 開發(fā)大型應(yīng)用成為觸手可及的現(xiàn)實(VScode)??v觀 JS 模塊化的發(fā)展,其中很多思想都借鑒了其他優(yōu)秀的動態(tài)語言(Python),然后結(jié)合 JS 運行環(huán)境的特點,衍生出符合自身的標(biāo)準(zhǔn)。但其實在本質(zhì)上,瀏覽器端的 JS 仍沒有真正意義上的支持模塊化,只能通過工具庫(RequireJS、SeaJS)或者語法糖(ES Module)去 Hack 實現(xiàn)模塊化。隨著 Node 前端工程化工具的繁榮發(fā)展(Grunt/Gulp/webpack),使我們可以不關(guān)注模塊化的實現(xiàn)過程,直接享受 JS 模塊化編程的快感。
在復(fù)習(xí) JS 模塊化的過程中,對 Webpack 等工具的模塊化語法糖轉(zhuǎn)碼產(chǎn)生了新的興趣,希望有時間可以去分析一下模塊化的打包機(jī)制和轉(zhuǎn)譯代碼,然后整理出來加深一下自己對模塊化實現(xiàn)原理的認(rèn)識和理解。
期待下一篇。
參考文章:
ECMAScript 6 入門 -- 阮一峰
JS 模塊化歷程
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/95773.html
摘要:在項目配置中的探索持續(xù)更新中后臺配置一項目需求請認(rèn)真看目錄結(jié)構(gòu)項目構(gòu)建很大都基于目錄來的線上用戶訪問的目錄線上資源文件目錄前端開發(fā)目錄一個業(yè)務(wù)需求模塊每個業(yè)務(wù)需求模塊下會有很多頁面 WebPack在項目配置中的探索(持續(xù)更新中) webpack + gulp + vue (thinkPHP后臺配置) 一、項目需求(請認(rèn)真看目錄結(jié)構(gòu),項目構(gòu)建很大都基于目錄來的) --- Applicat...
摘要:模塊化編程,已經(jīng)成為一個迫切的需求。隨著網(wǎng)站功能逐漸豐富,網(wǎng)頁中的也變得越來越復(fù)雜和臃腫,原有通過標(biāo)簽來導(dǎo)入一個個的文件這種方式已經(jīng)不能滿足現(xiàn)在互聯(lián)網(wǎng)開發(fā)模式,我們需要團(tuán)隊協(xié)作模塊復(fù)用單元測試等等一系列復(fù)雜的需求。 隨著網(wǎng)站逐漸變成互聯(lián)網(wǎng)應(yīng)用程序,嵌入網(wǎng)頁的Javascript代碼越來越龐大,越來越復(fù)雜。網(wǎng)頁越來越像桌面程序,需要一個團(tuán)隊分工協(xié)作、進(jìn)度管理、單元測試等等......開發(fā)...
閱讀 2958·2023-04-26 01:52
閱讀 3485·2021-09-04 16:40
閱讀 3641·2021-08-31 09:41
閱讀 1783·2021-08-09 13:41
閱讀 578·2019-08-30 15:54
閱讀 2972·2019-08-30 11:22
閱讀 1627·2019-08-30 10:52
閱讀 958·2019-08-29 13:24