1. 程式人生 > >js模組化的總結

js模組化的總結

從前端打包的歷史談起

  在很長的一段前端歷史裡,是不存在打包這個說法的。那個時候頁面基本是純靜態的或者服務端輸出的, 沒有 AJAX,也沒有 jQuery。Google 推出 Gmail 的時候(2004 年),XMLHttpRequest, 也就是我們俗稱的 AJAX被拾起的時候,前端開發者開始往頁面裡插入各種庫和外掛,我們的 js 檔案程指數倍的開始增加了。JSMin、YUI Compressor、Closure Compiler、UglifyJS 等 js 檔案壓縮合並工具陸陸續續誕生了。壓縮工具是有了,但我們得要執行它,最簡單的辦法呢,就是 windows 上搞個 bat 指令碼,mac / linux 上搞個 bash 指令碼,哪幾個檔案要合併在一塊的,哪幾個要壓縮的,釋出的時候執行一下指令碼,生成壓縮後的檔案。

  這時候commonJS出現了,如果想在a.js引入b.js和c.js,大概語法是

var b = require(./b.js);
var c = require(./c.js);

  b.js和c.js匯出方式是大概這樣的

exports.add = function(a, b) {
  return a+b;        
}

  然後再a.js中可以使用b模組的add方法

var n = b.add(5, 8);

 

// b.js
module.exports = {
  add(a, b){
    return a+b;
  }    
}

// or
exports.add = function(a,b) {
   return a+b; 
}

// a.js
var b = require('./b.js');
var n = b.add(5,8);

  上述程式碼中, module.exports 和 exports 很容易混淆,下面看下大致的內部實現

 

var module = require('./b.js');
module.add 
// 包裝了一層立即執行函式,防止全域性變數汙染,重要的是module,module是node獨有的一個變數
module.exports = {
  add: function(a,b) {
    return a+b;      
  }    
}

// 基本實現
var module = {
  exports: {}  // exports是個空物件  
}
var exports = module.exports
var load = function(module) {
  // 需要匯出的東西
  var add = function(a, b) {
    return a+b;  
  }    
  module.exports = add;
  return module.exports;    
}

  module.exports和exports用法一模一樣,但是不能對exports直接賦值,沒有任何效果。

 

  但是commonJS是node獨有的規範,瀏覽器中並不適用,因為require()的返回是同步的,伺服器載入資源的時候並不能非同步的讀取。在瀏覽器中如果使用這種方式就會堵塞js指令碼的執行,所以瀏覽器中只能使用Browserify解析。Browserify和webpack幾乎同時出現,簡單介紹下Browserify: 其目的是讓前端也能使用commonJS的語法來載入js,它會從入口js開始,把所有的require()呼叫檔案打包併合併到一個檔案,這樣就解決了非同步載入的問題。但是對比webpack,他的缺點也是明顯的: 

  1.不支援按需載入,Browserify不支援把程式碼打包成多個檔案。

  2.對非js檔案的載入不夠完善,比如html中的img標籤,只能轉成Data URI的形式,並不能替換為打包後的路徑。在配合gulp或者grunt使用時增加了難度。

  3.只支援commonJS規範,不支援後續介紹的AMD和ES6 Module。

  所以webpack一統了天下。

  於是,在commonJS的基礎上,2011又出現了 Asynchronous Module Definition,也是就是AMD規範,AMD規範使用非同步回撥的語法來並行下載多個依賴項,基本語法如下

require(['./b.js', './c.js'], function(b,c){
   var n = b.add(5,8);  
  console.log(c) })

  同時,匯出的時候也需要使用非同步回撥的方式,比如,c模組又依賴了d.js

defined(['./d'], function(d){
   return d.PI
})

  總結下AMD: 定義模組使用defined()函式,引入使用reqire()函式,兩者的區別是,前者必須要在回撥函式中返回一個值作為匯出的東西,後者確不需要任何匯出,同時也無法作為被依賴項被其他檔案匯入,因此一般用於入口檔案。

  AMD是由RequireJS提出的。如果想了解可以檢視其文件。但是現在基本已經被淘汰了。

  js的模組化問題解決後,css模組化也被各種各樣的css前處理器所處理: less、sass、stylus、scss等等。

  後來有了ES6規範,提出了模組化的概念,配合babel可以直接使用 ES6模組化

  

// b.js 
export function a() {}
export function b() {}

// c.js
export default function() {}

// a.js
import {a, b} from './b.js'
import ModuleC from './c.js'

  對於commonJS和ES6 module 的區別簡單總結如下:

  1. 前者支援動態匯入,也就是可以這樣寫 require(`${path}/b.js`), 後者目前不支援,但是已有提案。

  2. 前者是同步匯入,因為用於伺服器,檔案都在本地,同步匯入即使卡主主執行緒也影響不大;後者是非同步匯入,因為用於瀏覽器,需要從伺服器下載檔案,如果使用同步匯入會影響頁面渲染。

  3. 前者在匯出時是值拷貝,就算匯出的值發生變化了,匯入的值也不會改變。所以如果匯入結束後想更新值,必須重新匯入一次;後者才用的是實時繫結的方式, 匯入匯出的值都指向同一個記憶體地址,所以匯入值會跟隨匯出值變化。

  4. ES6 module 會編譯成為 require/exports 來執