Javascript模塊化發(fā)展,前端的血淚史。

前言

?
Javascript 模塊的演化歷史一定程度上代表了前端的發(fā)展史。從早期的 對(duì)象字面量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è)夢(mèng)想脫穎而出為使命,1280元祁門做網(wǎng)站,已為上家服務(wù),為祁門各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:13518219792

本文從四個(gè)階段來講述 JS 模塊化的發(fā)展歷程,旨在讓大家了解 JS 模塊化是如何發(fā)展到今天,每個(gè)模塊方案在當(dāng)時(shí)又解決了什么問題。

認(rèn)知革命

?
Javascript模塊化發(fā)展,前端的血淚史。

Javascript 早期誕生的目的用于客戶端驗(yàn)證表單,提高用戶體驗(yàn)。站在今天的解決方案角度去回顧,在那個(gè)無樣式,無交互的,簡(jiǎn)單的不能再簡(jiǎn)單的 Web 頁(yè)面,很難想 JS 的模塊化意義在哪里。

如果非要達(dá)到一定程度代碼復(fù)用,對(duì)象字面量完全可以滿足 Web 互聯(lián)網(wǎng)早期的需求。
?

//Person.js
var Person = {
    say: function(words) {
        //code
    },
    run: function() {
        //code
    }
};

Person.say('say somthing');
Person.run();

歷史總會(huì)進(jìn)步,互聯(lián)網(wǎng)上 Web 頁(yè)面越來越多樣化,好在人們會(huì)不斷的根據(jù)變化的需求調(diào)整模塊化的方式。
?
當(dāng)團(tuán)隊(duì)相互合作,去完成某一個(gè)項(xiàng)目時(shí),對(duì)象字面量缺點(diǎn)就一覽無遺。命名沖突、作用域隔離等問題就不可避免的會(huì)發(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();

這種模式,能任意定義不會(huì)被外界訪問到局部變量,也不會(huì)污染全局作用域,同時(shí)還能訪問全局中的一些變量。通過傳參命名空間,可將模塊掛在到全局 Person 命名空間上。

IIEF的模塊化方式,早已***到前端開發(fā)的基因。直到今天,在我們的日常開發(fā)中,都能見到或用到這種方式。

農(nóng)業(yè)革命

Web2.0時(shí)代的到來,網(wǎng)站應(yīng)用更加注重用戶與服務(wù)的雙向交互,前端開發(fā)也逐漸承擔(dān)更多的責(zé)任。一個(gè)網(wǎng)站,可能有成百上千的頁(yè)面,而且,javascrpt 不局限于客戶端。

commonjs

推崇 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)接口用于對(duì)外暴露模塊。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 對(duì)象上,否則其他文件是訪問不到該作用域的定義的任何數(shù)據(jù)。
在 nodejs 中,一個(gè) js 文件擁有訪問其他模塊(文件)能力,這就很好的解決模塊間相互依賴的問題。并且所有文件都是在服務(wù)器本地加載,速度極快。
但瀏覽器客戶端的現(xiàn)狀是殘酷的??聪旅胬?,如果某個(gè)頁(yè)面依賴Slider, Dialog, Tab模塊,而這三個(gè)模塊又有一些自身的依賴。

<!-- 模塊自身的依賴 -->
<script src="./util/Animation.js"></script>
<script src="./util/Mask.js"></script>

<!-- 模塊依賴 -->
<script src="./Slider/index.js"></script>
<script src="./Dialog/index.js"></script>
<script src="./Tab/index.js"></script>

<script>
    Slider();
    Dialog();
    Tab();
</script>

上面的例子可以看出:

  1. 全局作用域被污染。
  2. 開發(fā)人員必須手動(dòng)解決模塊依賴關(guān)系(順序)。
  3. 同步遠(yuǎn)程加載過多的文件,也會(huì)造成嚴(yán)重的頁(yè)面性能問題。
  4. 在大型,多人合作項(xiàng)目中,會(huì)導(dǎo)致整體架構(gòu)混亂。
    而通過工具browserify,可將commonjs規(guī)范移植到瀏覽器端,本質(zhì)上。browserify 是將所有被依賴commonjs的模塊,打包到當(dāng)前業(yè)務(wù)代碼中。
    AMD

    瀏覽器中的 js,本身并無加載其他文件(模塊)的接口。聰明的人們用動(dòng)態(tài)創(chuàng)建 script 節(jié)點(diǎn)實(shí)現(xiàn)了動(dòng)態(tài)加載模塊。
    AMD, 異步模塊定義,采用的是異步加載模塊方式。依賴模塊是異步加載,不會(huì)阻塞頁(yè)面的渲染。
    AMD規(guī)范中最核心的接口是definerequire,顧名思義:定義和加載模塊。
    其中以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ù)提供暴露出了各依賴模塊的接口。

UMD

此時(shí),模塊規(guī)范分成了commonjsAMD兩大陣營(yíng)。天下大勢(shì)分久必合,需要一種解決方案同時(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)的。

ES2015 Module

不管是 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è)模塊打包成單文件,會(huì)造成文件 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è)非常大的隱患,一旦命中條件判斷,你一整年的績(jī)效可能就都沒了。
那如果有編譯期間靜態(tài)檢查,會(huì)是怎樣的結(jié)果?
運(yùn)行 node --experimental-modules index.mjs 命令時(shí),控制臺(tái)會(huì)報(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 <anonymous>

這種編譯時(shí)靜態(tài)檢查對(duì)項(xiàng)目的質(zhì)量把控非常有用。
但 es6 module 有時(shí)候也讓我很憂傷。因?yàn)樗堋办`活”,所以給我?guī)砹死_。
來看看 import 語法:
Javascript模塊化發(fā)展,前端的血淚史。
再來看看 export 語法
Javascript模塊化發(fā)展,前端的血淚史。
額,其實(shí)我就想簡(jiǎn)單的 import/export 而已,“少即使多”。
農(nóng)業(yè)革命是前端史的重大進(jìn)步,社區(qū)各種模塊化解決方案以及事實(shí)上的標(biāo)準(zhǔn),從另一方面也推動(dòng)著 Javascript 從語言層面對(duì)模塊化進(jìn)行支持。這為我們架構(gòu)大型項(xiàng)目,保證項(xiàng)目質(zhì)量提供了機(jī)會(huì)。

三:工業(yè)革命

模塊的兼容問題以及重復(fù)勞動(dòng)應(yīng)該交給工具去做,我們應(yīng)該留出更多的時(shí)間享受”美好生活“。所以,涌現(xiàn)了一大批模塊化工具以及周邊的模塊管理工具。如Browserify、r.jsWebpack、Rollup、 jspmnpm、yarn等等。
各種工具極大的提高了我們的工作效率,也我們對(duì)模塊化有了更多的選擇。
快樂同時(shí)也帶來很多的痛,就是因?yàn)榭蛇x擇工具太多,配置太多,讓你深陷其中無法自拔。要么忙著寫bug, 要么忙著寫配置。

結(jié)語

科學(xué)革命的時(shí)代,還未到來。也許到那時(shí)候,模塊化的使用就像var m = 1;語法一樣,它在我們腦海里本應(yīng)該就是理所當(dāng)然的存在,而不需借助其他編譯、運(yùn)行等工具來實(shí)現(xiàn)。

當(dāng)前標(biāo)題:Javascript模塊化發(fā)展,前端的血淚史。
URL地址:http://bm7419.com/article38/jjegsp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、全網(wǎng)營(yíng)銷推廣網(wǎng)站內(nèi)鏈、品牌網(wǎng)站制作、App設(shè)計(jì)、企業(yè)網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)頁(yè)設(shè)計(jì)公司