【js 基礎系列】模塊化

前端在發展初期僅僅被用作製作一些簡單的頁面,此時的頁面邏輯比較簡單,所以也沒有太大的模塊化需求。但是隨着後期的發展,web 端的功能和邏輯越來越複雜,我們必須要有一套模塊化的規範去管理我們的應用。

前端模塊化的發展歷程十分曲折,大致的歷程如下
無模塊化規範 => CommonJS 規範 => AMD/CMD 規範 => ES6 Module

無模塊化規範

初期我們沒有統一的模塊化規範,但是我們本地的代碼還是需要邏輯清晰便於維護的,普遍的一些做法是使用立即執行的匿名函數去保證變量不被污染。

var module = (function($, _) {
	// code
} (jQuery, lodash));

這樣保證 module 內部自己的變量不會被外部污染,通過參數傳遞確定內部的依賴,是初期簡單的模塊化實現。詳細歷程在這裏,有興趣的同學可以去看下。

CommonJS

2009 年,nodejs 橫空出世,將 js 運用於服務端。由於服務端邏輯的複雜性,迫切的需要一種統一的模塊化規範。於是 nodejs 參照 CommonJS 的規範實現了模塊系統。

CommonJS 規範的核心思想是允許模塊通過 require 方法來同步加載所要依賴的其他模塊,然後通過 exportsmodule.exports 來導出需要暴露的接口。

// 導入
const fs = require('fs')
fs.readFileSync(new URL('file://hostname/p/a/t/h/file'))

const util = require('../util.js')

// 導出
module.exports = { util }
exports.util = function() {}

CommonJS 簡單方便的實現了 nodejs 的模塊引用,一直被應用於服務端的開發。但是由於其同步加載模塊的特性,導致客戶端使用時會出現很多問題。
在服務端,我們的模塊文件是從磁盤中讀取,速度非常的快,同步加載不會出現問題。但是在客戶端,網絡環境千差萬別,同步加載會阻塞其他腳本的執行,所以在客戶端我們需要一種異步加載的模塊化規範。

module.exportsexports 是有差別的,module.exports 本身就是一個對象,而 exports 是指向 module.exports 的一個引用,所以在使用上我們使用上面的方式去導出,而 exports = { util } 的方式會重寫 exports

AMD、CMD

AMD 是 Asynchronous Module Definition 的縮寫,意爲“異步模塊定義”,它是爲瀏覽器環境設計的模塊化規範。 RequireJS 是基於 AMD 規範實現的。

AMD 規範的語法如下

define(id?: String, dependencies?: String[], factory: Function|Object);

它要在聲明模塊的時候指定所有的依賴 dependencies,並且還要當做形參傳到 factory 中,對於依賴的模塊提前執行,依賴前置。

  • id 是模塊的名字,它是可選的參數。

  • dependencies 指定了所要依賴的模塊列表,它是一個數組,也是可選的參數,每個依賴的模塊的輸出將作爲參數一次傳入 factory 中。如果沒有指定 dependencies,那麼它的默認值是 ["require", "exports", "module"]

    define(function(require, exports, module) {})
    
  • factory 是最後一個參數,包裹着模塊的具體實現,它是一個函數或者對象。如果是函數,那麼它的返回值就是模塊的輸出接口或值。

我們定義一個 math 模塊,依賴於 jQuery 和一個我們本地的工具函數 util.js

// 定義
define('math', ['jQuery', './util.js'], function($, util) {
	// code
	const util = function() {}

	// util 就是模塊對外輸出的接口
	return util;
})

// 使用
require(['math'], function(math) {})

也可以在內部引用其他模塊(注:這裏不屬於 AMD 的規範,是 RequireJS 2.0 加入的,對 SeaJS 寫法的兼容,使 RequireJS 也支持按需加載)

// 定義
define('math', function(require) {
	const $ = require('jQuery')
	const util = require('./util.js')
	
	// code
	const util = function() {}
	
	// 也可以 exports 導出
	return util;
})

// 使用
require(['math'], function(math) {})

在使用時我們需要用 require 函數引用模塊。

require(['jQuery', 'lodash'], function($, _) {
	// code
}, function(err) {
	// error
})

require 接收三個參數。

  • 第一個參數是一個數組,表示所依賴的模塊,上例就是[‘jQuery’, ‘lodash’],即主模塊依賴這兩個模塊。
  • 第二個參數是一個回調函數,當前面指定的模塊都加載成功後,它將被調用。加載的模塊會以參數形式傳入該函數,從而在回調函數內部就可以使用這些模塊。
  • 第三個參數是處理錯誤的回調函數,接受一個error對象作爲參數。

可以去查看規範的細節,AMD 規範

CMD 是 Common Module Definition 的縮寫,SeaJS 是基於 CMD 規範實現的。
CMD 規範規定,一個文件就是一個模塊,定義語法如下

define(factory)

factory 可以是一個函數、對象、數組、字符串。factory 爲對象、數組、字符串時,表示模塊的接口就是該對象、字符串。

define({ "foo": "bar" })
define(['foo', 'bar']);
define('I am a template. My name is {{name}}.')

factory 是一個函數時,表示是模塊的構造方法。執行該構造方法,可以得到模塊向外提供的接口。actory 方法在執行時,默認會傳入三個參數:requireexportsmodule

define(function(require, exports, module) {
	const util = require('./util.js')
  	// code	
  	
  	// 導出模塊,也可以直接 return
  	exports.math = function() {}
})

具體的細節可以去這裏查看,CMD 規範CMD 規範2

最後 AMD 和 CMD 的區別

  1. AMD 是 RequireJS 在推廣過程中對模塊定義的規範化產出,CMD 是 SeaJS 在推廣過程中對模塊定義的規範化產出。
  2. 對於依賴的模塊,AMD 是提前執行,CMD 是延遲執行。(RequireJS 從 2.0 開始,也改成可以延遲執行,根據寫法不同,處理方式不同)。AMD 在開始時會加載所有的依賴模塊,而 CMD 推崇使用時加載。
    // CMD
    define(function(require, exports, module) {
    	const a = require('./a')
    	a.doSomething()
    	// code ...
    	const b = require('./b') // 依賴可以使用時書寫
    	b.doSomething()
    })
    
    // AMD
    define(['./a', './b'], function(a, b) {
    	// 依賴必須一開始就寫好
    	a.doSomething()
    	// code ...
    	b.doSomething()
    })
    
    
  3. AMD 的加載速度會稍優於 CMD ,但是現代的瀏覽器可以忽略。
  4. CMD 規範中,沒有全局的 require 概念,而是根據模塊系統的完備性,提供 seajs.use 來實現模塊系統的加載啓動。

最後附:RequireJS 和 SeaJS 的區別

ES6 Module

ES6 的模塊化,大家可以直接去看阮一峯老師的教程

ES6 Module 和其他模塊規範較大的區別是:

  • ES6 靜態的 import 引入的模塊,是強制開啓嚴格模式的。
    詳細可以查看 MDN 的描述

    靜態的 import 語句用於導入由另一個模塊導出的綁定。無論是否聲明瞭 strict mode ,導入的模塊都運行在嚴格模式下。

  • ES6 的引入,是對變量的引入,對原文件中值的改變會影響到 import 導入的變量。(這裏只有 export 導出的模塊是變量導出,export default 導出的是值,不會影響)

    // a.js
    var count = 1
    function add () {
    	return count++
    }
    export { count, add }
    
    // b.js
    import { count, add } from './a.js'
    console.log(count) // 1
    add()
    console.log(count) // 2
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章