JavaScript 模組化前世今生

查小小飛發表於2019-12-17

你寫你的,我寫我的,模組化!

為啥需要JS模組化

你想想,如果你和另一個前端兩個人共同開發一個專案,你負責實現A功能,他負責實現B功能,兩個人加班加點各自完成了各自的功能,你寫的檔案是a.js,他寫的檔案是b.js。然後你們兩個很開心,覺得大功告成了,然而將你們的程式碼載入到同一個專案時,發現,WTF,怎麼各種報錯。然後你們仔細看報錯,檢查程式碼,終於發現了問題,原來a.js裡面存在了一個名叫render的函式,b.js裡面也定義了一個名為render的函式,a.js裡面暴露了一個名為slider的全域性變數,b.js裡也暴露了一個slider全域性變數。。

這就是最大的一個問題,當多個人共同開發一個專案的時候,如何保證,別人寫的程式碼不會和你的衝突,這就是JS模組化要解決的最大的問題。

模組化的遠古時期

雖然現在ES6已經在語言層面實現了模組化,那在這之前,千千萬萬的JS開發者又是如何解決這個問題的呢?

函式封裝

// 一個函式做一個功能
function method1() {}
function method2() {}
複製程式碼

問題: 函式名就是全域性變數,容易命名衝突

NameSpace 名稱空間

var myJS = {
   var a = 1
   method1: function () {
     xxx
   }
   method2: function () {
    xxx
   }
}

myJS.method1() 
myJS.a = 2 // 外部可以修改內部成員
複製程式碼

問題:外部能夠隨便修改這個物件,不夠安全啊。

IIFE 模式

就是搞一個區域性作用域,別人訪問不到裡面的資料。

var module = !function() {
    var a = 1
    var method1 = function () {
        console.log(a)
    }
    return {
        method1:method1
    }
}()
複製程式碼

將你的程式碼全放在一個匿名函式裡面,這樣外部無法訪問到你的程式碼和資料,不會出現命名衝突。 然後執行這個函式,將這些資料暴露給外部,這樣外部就可以使用到這些資料。

JS 模組化近現代史

上面的思想和方法都是前人們在解決模組化的歷史過程中留下的,可見,一門語言的進化是多麼漫長而充滿坎坷的,哈哈哈,我們看完遠古時期,再看看近現代歷史。 由於JS天生不支援模組化,那麼就需要開發者們共同遵守一些開發規範,大家都按這個規範去寫程式碼,這樣就能夠很好的實現模組化的效果。那麼都有哪些規範呢?

  • CommonJS: Node環境下的規範
  • AMD: 瀏覽器環境下的規範 有 RequireJs
  • CMD: SeaJS

CommonJS

Node環境中,模組的寫法是按照CommonJS規範寫的,模組的規範核心就是,如何定義一個模組和如何載入一個模組。CommonJS 的規範 如下:

定義一個模組

一個JS檔案就是一個模組,有自己的作用域,裡面的程式碼資料是私有的,獨立的,對其他檔案不可見, 一個檔案就是一個模組。

var a = 1
var add = function () {
    console.log(a)
}
console.log(module)  // module 就是當前模組
複製程式碼
暴露資料

module表示當前模組,他是一個物件,物件有一些屬性,其中,通過module.exports 屬性將本模組的資料暴露出去。 一個模組只有一個出口,那就是 module.exports物件,將需要暴露的資料放在這個物件裡就可以。

var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;  // 暴露x
module.exports.addX = addX; //暴露 addX

或者這樣寫:
module.exports = {
    x: x,
    addX: addX 
}
複製程式碼
載入模組

require 命令載入一個模組

var module1 = require('./module1.js')  // 讀取module1.js 並執行這個檔案,返回值是 module1.js 的 exports物件

console.log(module1.x)
module1.addX()
複製程式碼

CommonJS 是在Node開發中用的規範,CommonJS規範載入模組是同步的,也就是說,只有載入完成,才能執行後面的操作。

Asynchronous Module Definition (AMD) :非同步模組定義

CommonJS規範載入模組是同步的,也就是說,只有載入完成,才能執行後面的操作。AMD規範則是非同步載入模組,允許指定回撥函式。由於Node.js主要用於伺服器程式設計,模組檔案一般都已經存在於本地硬碟,所以載入起來比較快,不用考慮非同步載入的方式,所以CommonJS規範比較適用。但是,如果是瀏覽器環境,要從伺服器端載入模組,這時就必須採用非同步模式,因此瀏覽器端一般採用AMD規範。

AMD本身是一種規範,而不是具體的實現,具體的實現有 RequireJsRequireJs是一個庫,這個庫的作用就是讓瀏覽器能夠解析你寫的模組化的JS。 下面就是RequireJs的基本用法。

具體請參考RequireJs的官方文件:RequireJs

定義模組

語法: define(id,dependencies,factory)
define()函式接受三個引數:
id: 模組的名字,但一般不要,就使用檔名作為模組名
dependencies: 一個陣列,成員是該模組的依賴,依賴引數是可選的,如果忽略此引數,它應該預設為["require", "exports", "module"]。
factory: 一個物件或是一個函式,代表該模組的輸出,如果是物件,這個物件就是這個模組,如果是函式,這個函式得返回值就是 該模組得內容

示例1:定義一個簡單得模組
define({
    name: "小飛"
})

示例2:通過函式返回值模組內容
define(function(){
    var a = 1,
    var getA = function(){
        console.log(a)
    }
    return getA   // 這就是這個模組得輸出
})

示例3:定義一個有依賴的模組
define(['./moduleA','./moduleB'],function(a,b){ //函式的引數是 依賴的模組的輸出
    return {
        method1: a.method1,
        nethod2: b.method2
    }
})

示例3: 如果一個模組的依賴有很多個,那麼你可以這樣去寫
define(function(require){ // require 預設的一個引數,是一個函式,require()用於載入一個模組,require方法也可以用在define方法內部。
    var m2 = require('./module2')
    var m3 = require('./module3')
    return {
        m2:m2,
        m3:m3
    }
})
//  AMD保留了commonjs中的require、exprots、module這三個功能。可以不把依賴羅列在dependencies陣列中。而是在程式碼中用require來引入
複製程式碼
載入模組

使用 require() 函式載入一個模組,在載入模組之前,要搞清楚一個關鍵:baseUrl

requirejs 在載入模組檔案的時候,路徑是按照: baseUrl + path尋找載入的,baseUrl 的設定如下:

  1. baseUrl 預設是 data-main 的目錄
  2. 沒有使用data-main 和 指定 baseUrl ,目錄就是引用 requirejsHTML所在目錄。

require()函式用於載入模組: 可以接受三個引數:

  • 第一個引數: 是一個陣列,成員是載入的模組
  • 第二個引數:是一個回撥函式,函式的引數是 載入的模組的是輸出,當依賴載入後就會執行 函式體就是你的程式碼
  • 第三個引數: 也是一個函式,用於處理錯誤的回撥,如果出現錯誤這個函式就會被執行,接受一個error物件作為引數
require(['./module-JS/module1'],function (m1) {
    console.log(m1)
})

// require()可以在 define()函式內部使用,但注意要確保 依賴了 require 模組,例如
define(function(require){
    var mod = require("./relative/name")
})
複製程式碼

還有很多很多用法和配置...,有興趣的話看文件吧,我沒有全部看完並實踐,因為我的目的不是學會用RequireJs這個庫,而是通過對模組化的演變而對JS模組化有一個完整的瞭解,我想這是很重要的。

Common Module Definition (CMD)

CMD SeaJS 在推廣過程中對模組定義的規範化產出。在 Sea.js 中,所有 JavaScript 模組都遵循 CMDCommon Module Definition) 模組定義規範。該規範明確了模組的基本書寫格式和基本互動規則。

JS 模組化的現在進行時

它來了,它來了,它帶著Module走來了,這是真正的革命,是新的篇章,是一個全新的時代,哈哈哈,不皮了。 這就是,ES6終於新增語法,在語言層面就實現了模組化。

ES6中 模組化的是由 exportimport 兩個語法實現的。

模組匯出

輸出變數:
export var a = 1; 
或者寫成:
var a = 1; 
export {year}; (推薦)

輸出函式:
export function f() {};
或者寫成:
function f() {}
export {f};
複製程式碼

模組匯入

import { firstName, lastName, year } from './profile.js';
複製程式碼

點選檢視詳情: ES6 Module語法

總結

JavaScript 的誕生就是為了實現一些簡單的指令碼驗證,JS之父用了十天的時間就發明創造了這門語言,設計的初衷和導致了這門指令碼語言必然存在著許許多多的不足,從JS模組化的發展看就能看到一門語言的發展和歷史潮流是密不可分的,瞭解這些思想和演變我覺得比學會用一個庫和工具更有價值。這是我在瞭解整個JS模組化的演變過程後,覺得要留下一點什麼東西,於是,就寫了這些文字。

相關文章