談談前端模塊化的演變歷程


前言

Javascript不是一種模塊化編程語言,它不支持"類"(class),更遑論"模塊"(module)了,隨着前端發展對
模塊需求越來越大,模塊也是經歷了從最初的簡單模塊寫法到AMD和CMD規范的出現,再到ES6發布,目前已經可以
很方便的在Javascript中使用"類"和"模塊"了。

一、以前的寫法

1、原始寫法

function m1(){
    //...
  }

  function m2(){
    //...
  }

缺點 :
"污染"了全局變量,無法保證不與其他模塊發生變量名沖突,而且模塊成員之間看不出直接關系。

2、對象寫法
為了解決上面缺點,把模塊寫成一個對象,所有的模塊成員都放到這個對象里面

var module1 = new Object({

    _count : 0,

    m1 : function (){
      //...
    },

    m2 : function (){
      //...
    }

  });

使用

module1.m1();

存在缺點:
但是,這樣的寫法會暴露所有模塊成員,內部狀態可以被外部改寫。比如,外部代碼可以直接改變內部計數器的值。

module1._count = 5;

3、立即執行函數寫法

立即執行函數(IIFE),可以達到不暴露私有成員的目的。

var module1 = (function(){

    var _count = 0;

    var m1 = function(){
      //...
    };

    var m2 = function(){
      //...
    };

    return {
      m1 : m1,
      m2 : m2
    };

  })();

上面的寫法,外部代碼無法讀取內部的_count變量。

console.info(module1._count); //undefined

4、放大模式
如果一個模塊很大,必須分成幾個部分,或者一個模塊需要繼承另一個模塊

  var module1 = (function (mod){

    mod.m3 = function () {
      //...
    };

    return mod;

  })(module1);

上面的代碼為module1模塊添加了一個新方法m3(),然后返回新的module1模塊。

5、寬放大模式
在瀏覽器環境中,模塊的各個部分通常都是從網上獲取的,有時無法知道哪個部分會先加載。如果采用上一節的寫法,第一個執行的部分有可能加載一個不存在空對象,這時就要采用"寬放大模式"。

var module1 = ( function (mod){

    //...

    return mod;

  })(window.module1 || {});

與"放大模式"相比,"寬放大模式"就是"立即執行函數"的參數可以是空對象。

6、輸入全局變量
獨立性是模塊的重要特點,模塊內部最好不與程序的其他部分直接交互。
為了在模塊內部調用全局變量,必須顯式地將其他變量輸入模塊

var module1 = (function ($, YAHOO) {

    //...

  })(jQuery, YAHOO);

上面的module1模塊需要使用jQuery庫和YUI庫,就把這兩個庫(其實是兩個模塊)當作參數輸入module1
好處:
這樣做除了保證模塊的獨立性,還使得模塊之間的依賴關系變得明顯

二、CommonJS、AMD和CMD的出現

談到AMD和CMD,不得不說下CommonJS。2009年,美國程序員Ryan Dahl創造了node.js項目,將javascript語言用於服務器端編程,
由於瀏覽器端網頁還比較簡單 ,對於模塊不是特別依賴,但在服務器端因為要與操作系統和其他應用程序互動,CommonJS
就在這樣的背景下誕生了。

引入第三方模塊並調用方法

var math = require('math');
math.add(2,3); // 5

這里有一個CommonJS模塊使用的例子

//模塊定義 myModule.js
var name = 'Byron';
function printName(){
    console.log(name);
}
function printFullName(firstName){
    console.log(firstName + name);
}
module.exports = {
    printName: printName,
    printFullName: printFullName
}
//加載模塊
var myModule = require('./myModule.js');
myModule.printName();

1、AMD規范

想象一下如果把這段代碼放到瀏覽器端 ,math.add(2, 3),在第一行require('math')之后運行,因此必須等math.js加載完成。也就是說,如果加載時間很長,整個應用就會停在那里等

var math = require('math');
  math.add(2, 3);

這對服務器端不是一個問題,因為所有的模塊都存放在本地硬盤,可以同步加載完成,等待時間就是硬盤的讀取時間。但是,對於瀏覽器,這卻是一個大問題,因為模塊都放在服務器端,等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於"假死"狀態

因此,瀏覽器端的模塊,不能采用"同步加載"(synchronous),只能采用"異步加載"(asynchronous)

AMD也采用require()語句加載模塊,但是不同於CommonJS,它要求兩個參數

require([module], callback);

上面代碼改寫成AMD形式就是這樣

require(['math'], function (math) {

    math.add(2, 3);

  });

目前實現AMD規范的有RequireJS和curl.js

RequireJS模塊例子:

// 定義模塊 myModule.js
define('myModule', ['dependency'], function(){
    var name = 'Byron';
    function printName(){
        console.log(name);
    }
    return {
        printName: printName
    };
});

// 加載模塊
require(['myModule'], function (my){
  my.printName();
});

2、CMD規范
CMD: Common Module Definition通用模塊定義, 由國內發展出來, SeaJS是其典型代表, 即SeaJS是通過瀏覽器對CMD的具體實現

SeaJS模塊例子:

// 定義模塊  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js');
  var foo = require('foo');
  var out = foo.bar();
  $('div').addClass('active');
  module.exports = out;
});

// 加載模塊
seajs.use(['myModule.js'], function(my){

});

3、CommonJS、AMD和CMD區別

  • CommonJS是同步的, 主要用於服務器
  • AMD和CMD是異步的, 兩者的模塊定義和加載機制稍有不同, 主要用於瀏覽器
  • AMD推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊,CMD`推崇就近依賴,只有在用到某個模塊的時候再去require
    兩個都是定義的全局define函數來定義模塊, define接收函數function(require, exports, module)保持一致
  • CMD是懶加載, 僅在require時才會加載模塊; - AMD是預加載, 在定義模塊時就提前加載好所有依賴
  • CMD保留了CommonJS風格

三、ES6的使用

ES6在語言標准的層面上, 實現了模塊功能, 而且實現得相當簡單, 完全可以取代CommonJS和AMD規范, 是瀏覽器和服務器通用的模塊解決方案

ES6模塊例子:

//模塊定義 myModule.js
const name = 'Byron';
function printName(){
    console.log(name);
}
function printFullName(firstName){
    console.log(firstName + name);
}
const myModule = {
    printName: printName,
    printFullName: printFullName
};
export myModule;

//加載模塊
import myModule, { printFullName } from './myModule.js';
myModule.printName();
printFullName('Michael');

四、最后再說說browserify和webpack

說到 browserify / webpack ,那還要說到 seajs / requirejs 。這四個都是JS模塊化的方案。其中seajs / require 是一種類型,browserify / webpack 是另一種類型。

1、seajs / require :
是一種在線"編譯"模塊的方案,相當於在頁面上加載一個 CMD/AMD 解釋器。這樣瀏覽器就認識了 define、exports、module 這些東西。也就實現了模塊化。

2、browserify / webpack : 是一個預編譯模塊的方案,相比於上面 ,這個方案更加智能。沒用過browserify,這里以webpack為例。首先,它是預編譯的,不需要在瀏覽器中加載解釋器。另外,你在本地直接寫JS,不管是 AMD / CMD / ES6 風格的模塊化,它都能認識,並且編譯成瀏覽器認識的JS。

參考閱讀:

http://www.ruanyifeng.com/blog/2012/10/javascript_module.html
https://www.jianshu.com/p/5226bd9644b6
http://es6.ruanyifeng.com/#docs/module


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM