JavaScript 中的模組化
最早的基於立即執行函式,閉包的模組化
const MountClickModule = function(){
let num = 0;
const handleClick = ()=>{
console.log(++num);
}
return {
countClick:()=>{
document.addEventListener('click',handleClick)
}
}
}();
MountClickModule.countClick();
(function(module) {
module.say = ()=>{
console.log(num) //undefined
//do something
}
})(MountClickModule);
MountClickModule.say();
這種閉包的壞處:
- 擴充套件模組間無法訪問私有變數。
- 強依賴模組匯入的順序,專案變大後不好維護。
AMD 和 CommonJS 模組化JavaScript 應用
AMD 和 CommonJS 是兩個互相競爭的標準,均可定義 JavaScript 模組。除了語法和原理的區別之外,主要區別是 AMD 的設計理念是明確基於瀏覽器,而 CommonJS 的設計是面向通用 JavaScript 環境
使用 AMD 定義模組依賴
AMD 非同步模組定義規範制定了定義模組的規則,這樣模組和模組的依賴可以被非同步載入。這和瀏覽器的非同步載入模組的環境剛好適應(瀏覽器同步載入模組會導致效能、可用性、除錯和跨域訪問等問題)。
目前,AMD 最流行的實現是 RequireJS
。
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});
AMD 提供名為 aplha 的函式,它接收一下引數:
- 第一個引數,
id
,是個字串。它指的是定義中模組的名字,這個引數是可選的。如果沒有提供該引數,模組的名字應該預設為模組載入器請求的指定指令碼的名字。 - 第二個引數,dependencies,是個定義中模組所依賴模組的陣列。
- 第三個引數是初始化模組的工廠函式,該函式接收dependencies作為引數 AMD非同步獲取依賴,以避免阻塞,如果依賴來自伺服器,那麼這個過程將花費一些時間,知道依賴全部載入完成後,呼叫模組的工廠函式,並傳入所有的依賴。
可以看出,AMD 有一下幾項有點:
- 非同步載入模組,避免阻塞。
- 自動處理依賴,我們無需考慮模組的引入順序。
- 在同一個檔案中可以定義多個模組。
CMD
CMD 是 SeaJS 在推廣過程中對模組定義的規範化產出,在 CMD 規範中,一個模組就是一個檔案。程式碼的書寫格式如下:
define(function (require, exports, module) {
const foo = require('./foo')
a.doSomething()
// ...
const bar = require('./bar') // 依賴可以就近書寫
b.doSomething()
// do something else
})
CommonJS
AMD 的設計明確基於瀏覽器,而 CommonJS 的設計是面向通用 JavaScript 環境。CommonJS 目前在 Nodejs 社群中具有最多的使用者。CommonJS 使用基於檔案的模組,所以每個檔案中都只能定義一個模組,CommonJs 提供變數 module,該變數具有屬性 exports,通過 exports 很容易擴充套件屬性。最後,module.exports 作為模組的公共介面。
const beta = require('beta');
function alpha(){
return beta.verb();
//Or:
return require("beta").verb();
}
module.exports = alpha;
CommonJS 要求一個檔案就是一個模組,檔案中的程式碼就是模組的一部分,所以不需要使用立即執行函式來包裝變數,在模組中定義的變數都是安全的再模組中,不會洩露到全域性作用域。只有通過 module.exports
物件暴露的物件或函式才可以在函式外部訪問 CommonJS 具有以下特點:
- 基於檔案系統。
- 引入模組時檔案同步載入,可以訪問模組的公共介面。
- 模組載入相對更快 這是 CommonJS 在服務端更流行的原因。
ES6 模組的匯入匯出
ES6 模組結合了CommonJS 和 AMD 的有點,具體如下:
- 與 CommonJS 類似,ES6模組語法相對簡單,並且基於檔案(每一個檔案就是一個模組)
- 與 AMD 類似,ES6 模組支援非同步載入模組。
既 ES6 結合了兩種模組化的有點,基於檔案系統,既支援非同步也支援同步,因為瀏覽器並沒有實現 ES6 的模組化 API 所以具體是非同步還是同步取決於loader api
ES6 模組的主要思想是必須顯示的使用標誌符匯出模組,才能從外部訪問模組。其他標誌符,甚至在最頂級作用域中定義的識別符號,只能在模組中使用。 ES6 引入兩個關鍵字:
- export ---- 從模組外部指定識別符號。
- import ---- 匯入模組識別符號。
從index.js模組中匯出:
const hello = 'hello';
export const name = 'yunfly'
export function sayHi(){
return `${hello} ${name}!`
}
也可以在模組最後一起匯出:
// foo.js
const hello = 'hello';
export const name = 'yunfly'
export function sayHi(){
return `${hello} ${name}!`
}
export { name, sayHi }
// export { name as firstName, sayHi }
// bar.js
// 使用 as 設定導如別名
import { name as firstName, sayHi } from 'foo'
console.log(name)
sayHi()
//bar2.js
// 匯出全部識別符號:
import * as sayModule from 'foo';
console.log(sayModule.name)
sayModule.sayHi()
// foo.js
class Foo {}
export default Foo
// bar.js
import Foo from './foo';
這存在一些可維護性的問題:
- 如果你在 foo.ts 裡重構 Foo,在 bar.ts 檔案中,它將不會被重新命名;
- 如果你最終需要從 foo.ts 檔案中匯出更多有用的資訊(在你的很多檔案中都存在這種情景),那麼你必須兼顧匯入語法。
- 在 ts 中預設匯出的可發現性非常差,你不能智慧的辨別一個模組它是否有預設匯出。
文章參考:《JavaScript忍者祕籍》