?
Javascript 模塊的演化歷史一定程度上代表了前端的發(fā)展史。從早期的 對象字面量
、IIFE
到后來的 commonjs
, AMD
等, 再到如今的 ES Module
。這些模塊化方案在互聯(lián)網(wǎng)技術(shù)發(fā)展需求下不斷革新,演進(jìn)。
成都創(chuàng)新互聯(lián)成立于2013年,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目做網(wǎng)站、成都做網(wǎng)站網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢想脫穎而出為使命,1280元祁門做網(wǎng)站,已為上家服務(wù),為祁門各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:13518219792
本文從四個(gè)階段來講述 JS 模塊化的發(fā)展歷程,旨在讓大家了解 JS 模塊化是如何發(fā)展到今天,每個(gè)模塊方案在當(dāng)時(shí)又解決了什么問題。
?
Javascript 早期誕生的目的用于客戶端驗(yàn)證表單,提高用戶體驗(yàn)。站在今天的解決方案角度去回顧,在那個(gè)無樣式,無交互的,簡單的不能再簡單的 Web 頁面,很難想 JS 的模塊化意義在哪里。
如果非要達(dá)到一定程度代碼復(fù)用,對象字面量
完全可以滿足 Web 互聯(lián)網(wǎng)早期的需求。
?
//Person.js
var Person = {
say: function(words) {
//code
},
run: function() {
//code
}
};
Person.say('say somthing');
Person.run();
歷史總會進(jìn)步,互聯(lián)網(wǎng)上 Web 頁面越來越多樣化,好在人們會不斷的根據(jù)變化的需求調(diào)整模塊化的方式。
?
當(dāng)團(tuán)隊(duì)相互合作,去完成某一個(gè)項(xiàng)目時(shí),對象字面量
缺點(diǎn)就一覽無遺。命名沖突、作用域隔離等問題就不可避免的會發(fā)生,只是一個(gè)時(shí)間早與晚的問題。
?
javascript 函數(shù),擁有者天然的局部作用域,外界是訪問不到函數(shù)內(nèi)部的作用域。自然而然過渡到IIFE
模塊化。
?
(function(global){
var Person = global.Person || {};
var pritiveFn = function(){
//other code
};
var pritiveName = 'Tom';
Person.say = function(words) {
pritiveFn();
console.log( pritiveName + 'say: ' + words);
//other code
}
Person.run = function() {
pritiveFn();
//other code
}
})(window);
Person.say();
Person.run();
這種模式,能任意定義不會被外界訪問到局部變量,也不會污染全局作用域,同時(shí)還能訪問全局中的一些變量。通過傳參命名空間,可將模塊掛在到全局 Person 命名空間上。
IIEF
的模塊化方式,早已***到前端開發(fā)的基因。直到今天,在我們的日常開發(fā)中,都能見到或用到這種方式。
Web2.0時(shí)代的到來,網(wǎng)站應(yīng)用更加注重用戶與服務(wù)的雙向交互,前端開發(fā)也逐漸承擔(dān)更多的責(zé)任。一個(gè)網(wǎng)站,可能有成百上千的頁面,而且,javascrpt 不局限于客戶端。
推崇 commonjs
模塊化規(guī)范的 Nodejs ,將模塊化推向了一個(gè)新的高度。
// path/ModuleA.js
var ModuleA = function(){
//code
}
module.exports = ModuleA;
//-------------------------
// path/ModuleB.js
var ModuleB = function(){
//code
}
module.exports = ModuleB;
//------------------------
// path/index.js
var ModuleA = require('./path/ModuleA');
var ModuleB = require('./path/ModuleB');
ModuleA();
ModuleB();
commonjs
規(guī)范提供 module.exports
(或者 exports
)接口用于對外暴露模塊。require
加載模塊。
仔細(xì)想想,日常開發(fā)中我們理所應(yīng)當(dāng)只關(guān)心模塊的自由導(dǎo)出和加載。而加載速度、依賴順序、作用域隔離等問題應(yīng)該交給框架或者其他科學(xué)技術(shù)來系統(tǒng)解決,讓我們無感知。
但,nodejs 畢竟是運(yùn)行在服務(wù)端的 javascript。
nodejs 中每個(gè)文件具有獨(dú)立的作用域,所以每個(gè)文件可認(rèn)為是一個(gè)模塊。除非你顯示的定義在全局 global 對象上,否則其他文件是訪問不到該作用域的定義的任何數(shù)據(jù)。
在 nodejs 中,一個(gè) js 文件擁有訪問其他模塊(文件)能力,這就很好的解決模塊間相互依賴的問題。并且所有文件都是在服務(wù)器本地加載,速度極快。
但瀏覽器客戶端的現(xiàn)狀是殘酷的??聪旅胬樱绻硞€(gè)頁面依賴Slider
, Dialog
, Tab
模塊,而這三個(gè)模塊又有一些自身的依賴。
上面的例子可以看出:
browserify
,可將commonjs
規(guī)范移植到瀏覽器端,本質(zhì)上。browserify
是將所有被依賴commonjs
的模塊,打包到當(dāng)前業(yè)務(wù)代碼中。
瀏覽器中的 js,本身并無加載其他文件(模塊)的接口。聰明的人們用動(dòng)態(tài)創(chuàng)建 script 節(jié)點(diǎn)實(shí)現(xiàn)了動(dòng)態(tài)加載模塊。AMD
, 異步模塊定義,采用的是異步加載模塊方式。依賴模塊是異步加載,不會阻塞頁面的渲染。
AMD規(guī)范中最核心的接口是define
和require
,顧名思義:定義和加載模塊。
其中以requirejs
代表,是AMD
規(guī)范的實(shí)現(xiàn)。
// 定義模塊
define(['path/util/Animation'], function(Animation){
// Slider code
return Slider;
});
// 加載執(zhí)行模塊
require(['path/Slider'], function(Slider){
Slider();
})
可以看出,接口的第一個(gè)參數(shù),代表模塊的依賴路徑。模塊或業(yè)務(wù)的代碼,放在 callback 中,其中 callback 參數(shù)提供暴露出了各依賴模塊的接口。
此時(shí),模塊規(guī)范分成了commonjs
和AMD
兩大陣營。天下大勢分久必合,需要一種解決方案同時(shí)兼容這兩種規(guī)范。而UMD
規(guī)范的誕生就是解決該問題。
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD 規(guī)范
define([], factory);
} else if (typeof exports === 'object') {
// commonjs 規(guī)范
module.exports = factory();
} else {
// 掛載到全局
root.globalVar = factory();
}
}(this, function () {
return {};
}));
從上面可以看出 UMD 是通過判斷運(yùn)行環(huán)境中是否存在各模塊化的接口來實(shí)現(xiàn)的。
不管是 commonjs, AMD, UMD,都是畢竟是為了彌補(bǔ) javascript 模塊缺陷而衍生出的民間解決方案。2015年es6的發(fā)布,讓javascript終于在語言層面上實(shí)現(xiàn)了模塊化。
?
// path/ModuleA.js
var ModuleA = function(){
//code
}
exports default ModuleA;
//-------------------------
// path/ModuleB.js
var ModuleB = function(){
//code
}
exports default ModuleB;
//------------------------
// path/index.js
import ModuleA from './path/ModuleA';
import ModuleB from './path/ModuleB';
ModuleA();
ModuleB();
commonjs
已經(jīng)發(fā)展很成熟,也能滿足日常需求。初略看,es module “就像是語法糖”,我們?yōu)楹芜€要去使用它呢,換句話說,我們是用它能為我們帶來哪些收益?
不管是 commonjs
, AMD
,他們的模塊架構(gòu)是 “動(dòng)態(tài)架構(gòu)”,換句話說,模塊依賴是在程序運(yùn)行時(shí)才能確定。而es module
是 “靜態(tài)架構(gòu)”,也就是模塊依賴在代碼編譯時(shí)就獲取到。所以在 commonjs 里能進(jìn)行 “動(dòng)態(tài)引入” 模塊。
if ( Math.random() > 0.5 ) {
require('./ModuleA');
} else {
require('./ModuleB');
}
而在 es module 中是無法進(jìn)行類似操作的。從這個(gè)角度來看,es6 module 靈活性還不如 commonjs。但事物具有兩面性。es6 module 其實(shí)能為我們帶來以下幾個(gè)收益。
tree shaking
在我們部署項(xiàng)目時(shí),常常需要將各個(gè)模塊文件打包成單個(gè)文件,以便瀏覽器一次性加載所有模塊,減少 reqeust 數(shù)量。因?yàn)樵?HTTP/1 中,瀏覽器 request 并發(fā)數(shù)量有限制。不過隨之帶來的問題是,多個(gè)模塊打包成單文件,會造成文件 size 過大。
如果我們能在編譯期時(shí)確定好模塊依賴,就可以消除沒有用到的模塊,以便達(dá)到一定程度的優(yōu)化,來看看下面例子。
// moduleA.js
export function moduleX(){
//some code
}
export function moduleY(){
//some code
}
// index.js
import { moduleX, moduleY } from './moduleA';
moduleX();
通過工具 Rollup, 可將 index.js 打包成如下代碼:
'use strict';
function moduleX(){
//some code
}
moduleX();
可以看出,打包的代碼只包含 moduleX,最大限度的減少了打包文件 size,這就是所謂的 'tree shaking', 讀者可以好好品味下這個(gè)詞,很傳神。
模塊變量靜態(tài)檢查
es6 module由于是“靜態(tài)架構(gòu)”,在編譯時(shí)就能確定模塊的依賴樹以及確保模塊一定是被正確的 import/export ,這就為項(xiàng)目質(zhì)量帶來很大的保障??聪旅胬?
// module1.mjs
export function moduleX(){
console.log(1);
}
// index.mjs
// 注意:module1.mjs 中并沒有 export 出 moduleY
import { moduleX, moduleY } from './module1.mjs';
moduleX();
//注意
let randomNum = Math.random();
if (randomNum) > 0.3 && randomNum < 0.4 ) {
moduleY();
}
?
如果沒有靜態(tài)檢查,在上面代碼中的條件判斷得出,代碼運(yùn)行期間,執(zhí)行 moduleY() 函數(shù)報(bào)錯(cuò)的概率是10%,這種風(fēng)險(xiǎn)在線上環(huán)境就是一個(gè)非常大的隱患,一旦命中條件判斷,你一整年的績效可能就都沒了。
那如果有編譯期間靜態(tài)檢查,會是怎樣的結(jié)果?
運(yùn)行 node --experimental-modules index.mjs
命令時(shí),控制臺會報(bào)錯(cuò):
import { moduleX, moduleY } from './module1.mjs';
^^^^^^^
SyntaxError: The requested module does not provide an export named 'moduleY'
at ModuleJob._instantiate (internal/loader/ModuleJob.js:88:21)
at
這種編譯時(shí)靜態(tài)檢查對項(xiàng)目的質(zhì)量把控非常有用。
但 es6 module 有時(shí)候也讓我很憂傷。因?yàn)樗堋办`活”,所以給我?guī)砹死_。
來看看 import 語法:
再來看看 export 語法
額,其實(shí)我就想簡單的 import/export 而已,“少即使多”。
農(nóng)業(yè)革命是前端史的重大進(jìn)步,社區(qū)各種模塊化解決方案以及事實(shí)上的標(biāo)準(zhǔn),從另一方面也推動(dòng)著 Javascript 從語言層面對模塊化進(jìn)行支持。這為我們架構(gòu)大型項(xiàng)目,保證項(xiàng)目質(zhì)量提供了機(jī)會。
模塊的兼容問題以及重復(fù)勞動(dòng)應(yīng)該交給工具去做,我們應(yīng)該留出更多的時(shí)間享受”美好生活“。所以,涌現(xiàn)了一大批模塊化工具以及周邊的模塊管理工具。如Browserify、r.js、Webpack、Rollup、 jspm、 npm、yarn等等。
各種工具極大的提高了我們的工作效率,也我們對模塊化有了更多的選擇。
快樂同時(shí)也帶來很多的痛,就是因?yàn)榭蛇x擇工具太多,配置太多,讓你深陷其中無法自拔。要么忙著寫bug, 要么忙著寫配置。
科學(xué)革命的時(shí)代,還未到來。也許到那時(shí)候,模塊化的使用就像var m = 1;語法一樣,它在我們腦海里本應(yīng)該就是理所當(dāng)然的存在,而不需借助其他編譯、運(yùn)行等工具來實(shí)現(xiàn)。