什么是模塊化
將一組模塊(及其依賴項)以正確的順序拼接到一個文件(或一組文件)中的過程。
傳統的模塊化做法。
模塊是實現特定功能的一組屬性和方法的封裝。
將模塊寫成一個對象,所有的模塊成員都放到這個對象里面。
var module1 = new Object({
_count:0,
f1:function(){},
f2:function(){}
})
module1.f1()
module1.f2()
上面的對象可以改變里面的屬性和方法,不安全
var module1 = (function(){
var count=0;
return {
f1:function(){},
f2:function(){}}
}());
module1.f1()
module1.f2()
module1.count //undefined
使用 將相應的方法和屬性封裝在函數中,這樣就不會暴露私有成員
利用構造函數封裝對象
function Father (){
var arr =[];
this.add = function (val){
arr.push(val)
}
this.toString = function(){
return arr.join('');
}
}
var a = new Father();
a.add(1);//[1]
a.toString();//"1"
a.arr // undefined
上面的函數將 arr
變成私有變量,在函數外部無法訪問,但是形成了閉包,非常耗費內存;
違背了構造函數與實例對象在數據上相分離的原則(即實例對象的數據,不應該保存在實例對象以外)。
function ToString() {
this._buffer = [];
}
ToString.prototype = {
constructor: ToString,
add: function (str) {
this._buffer.push(str);
},
toString: function () {
return this._buffer.join('');
}
};
雖然上面的構造函數未生成閉包,但是外部可以修改方法和屬性,不安全
放大模式
如果一個模塊很大或者一個模塊需要繼承另一個模塊可以利用立即執行函數的特效來封裝
var module1 = (function(m1){
mod1.col=function(){
console.log(this)
};
return mod1;
}(window.modlue2 ||{})) //有些模塊可能是null 確保函數正常執行 采用兼容模式 window.modlue2 ||{}
- 獨立性是模塊的重要特點,模塊內部最好不與程序的其他部分直接交互。
var module1 = (function ($, Swiper) {
//...
}(jQuery, Swiper));
上面的 module1 引入 jQuery 和 Swiper 當做兩個參數傳入模塊中,保證了模塊的獨立性,還使得模塊之間的依賴關系變得明顯。
立即執行函數還可以起到命名空間的作用。
(function($, window, document) {
function go(num) {
}
function handleEvents() {
}
function initialize() {
}
function dieCarouselDie() {
}
//attach to the global scope
window.finalCarousel = {
init : initialize,
destroy : dieCarouselDie
}
}( jQuery, window, document ));
以上都有一個共同點:使用單個全局變量箭頭代碼包裝在函數中,使用閉包建立私有空間
但是都有缺點:
- 不知道模塊(庫) 的加載順序
- 還是有可能引起命名沖突,比如兩個庫都有相同的名稱,或者使用哪個版本
有幾種良好實施的方法:CommonJS、AMD和CMD。可以解決以上的缺陷
CommonJS
-
CommonJS
是一種思想, 本質上是可復用的JavaScript,它導出特定的對象,提供其它程序使用。 -
由於
JavaScript
沒有模塊系統、標准庫較少、缺乏包管理工具,因此CommonJS
是為它的表現來制定規范。 -
每個JavaScript 文件 都將模塊存儲在自己獨有的作用域中。
-
需要使用
module.exports
和exports.obj
來導出對象,並在需要它的程序中使用require('module')
加載
//文件1
function myModule() {
this.hello = function() {
return 'hello!';
}
this.goodbye = function() {
return 'goodbye!';
}
}
module.exports = myModule;
//文件2
var myModule = require('myModule');
var myModuleInstance = new myModule();
myModuleInstance.hello(); // 'hello!'
myModuleInstance.goodbye(); // 'goodbye!'
實現原理
var module1 = {
export1:{}
};
(function (module,exports){
exports.add = functon(val){
return val *10
}
}(module1,module1.export1));
var fn = module1.export1.add;
fn(2)//20
利用立即執行函數 接受兩個參數 module 和 exports, 模塊就通過立即執行函數賦值,然后導出模塊,即可實現模塊的加載
這種方法的好處:
- 避免全局污染
- 明確依賴項目
- 語法清晰
缺點: - 由於
CommonJS
采用服務器優先方法並且同步加載模塊,因此在瀏覽器中使用它會阻止瀏覽器運行其他內容,直到加載完成。
我們可以使用 AMD
來異步加載
AMD(Asynchromous Module Definition)
- 定義了一套 JavaScript 模塊依賴異步加載標准,來解決同步加載的問題。
- AMD模塊加載不影響后面語句的運行。所有依賴某些模塊的語句均放置在回調函數中。
- 定義了一個函數
define
,通過define
方法定義模塊。
define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {
console.log(myModule.hello());
});
上面的 define
函數將每個模塊的依賴項,以數組的形式作為參數。
這些依賴項會在后台異步加載,一旦加載完成,
define
函數就調用模塊給出的回調函數
myModule
可能像下面一樣定義:
define([], function() {
return {
hello: function() {
console.log('hello');
},
goodbye: function() {
console.log('goodbye');
}
};
});
CMD(Common Module Definition)
CMD
由玉伯大佬提出並用於SeaJS- CMD 和 AMD 很相似,都有 define 函數, 通過 require 加載
CMD和AMD 不同點:
- 對於依賴的模塊 CMD 延遲執行, AMD 提前執行(requireJS 高版本也開始延遲執行)
- CMD使用依賴就近原則(按需加載):
define(function(require, exports, module) {
var near = require('./a')
near.doSomething()
// 此處略去 100 行
var nearOne = require('./b') // 依賴可以就近書寫
nearOne.doSomething() // ...
})
- AMD使用依賴前置原則(必須先加載完依賴):
define(['./a', './b'], function(nearTow, nearThree) { // 必須一開始加載
nearTow.doSomething()
// 此處略去 100 行
nearThree.doSomething()
...
})
CMD
里,沒有全局 require,而是根據模塊系統的完備性,提供 seajs.use 來實現模塊系統的加載啟動。CMD 里,每個 API 都簡單純粹。AMD
的 API 默認是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。
AMD
和 CommonJS
不同點:
AMD
:
- 采用瀏覽器優先的方法,異步加載,主要用於瀏覽器
- 先加載依賴項
- 依賴項可以說 對象、函數、構造函數、字符串等等其他JS類型
CommonJS
:
- 采用服務器優先的方法,同步加載,主要用於服務器
- 支持對象作為模塊
共同點: 先加載依賴項
通用模塊定義 UMD
同時支持
AMD
和CommonJS
本質 創建了一種方法來使用兩者的任何一種,同時支持全局變量定義,(JS兼容性的常用思想)所以UMD
可以在客戶端和服務器上工作
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['myModule', 'myOtherModule'], factory);
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('myModule'),
require('myOtherModule'));
} else {
root.returnExports = factory(root.myModule, root.myOtherModule);
}
}(this, function (myModule, myOtherModule) {
function notHelloOrGoodbye(){};
function hello(){};
function goodbye(){};
return {
hello: hello,
goodbye: goodbye
}
}));
ES6模塊(即 ES2015/ECMAScript 6、ES6)
- 使用
import
關鍵字引入模塊,通過export
關鍵字導出模塊 - ES6目前無法在瀏覽器中執行,只能通過babel將不被支持的import編譯為當前受到廣泛支持的 require。
//a.js
export let cun =1;
export function add() {
cun++;
}
//----------------
import { cun, add } from './a.js';
console.log(cun); // 1
incCounter();
console.log(cun); // 2
export var fo ='a';
setTimeout(() => fo ='b',500);
import {fo} from './a.js';
console.log(fo);//'a'
setTimeout(()=> console.log(fo),500)//'b'
//ES6 輸入的模塊變量,只是一個“符號連接”,所以這個變量是只讀的,對它進行重新賦值會報錯。
fo = 's' //error
- ES6 模塊之中,頂層的this指向undefined,即不應該在頂層代碼使用this。
CommonJS
、AMD
和CMD
相比: ES6
模塊是動態引用,並且不會緩存值,模塊里面的變量綁定其所在的模塊。- ES6 對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。
- ES6 module編譯時輸出接口(加載),輸出的是值的引用。(靜態編譯)
- CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完才會生成。
- CommonJS 模塊運行時加載,輸出的是一個值的拷貝。(動態編譯)
一旦輸出一個值,模塊內部的變化就影響不到這個值。
// lib/counter.js
var counter = 1;
function increment() {
counter++;
}
function decrement() {
counter--;
}
module.exports = {
counter: counter,
increment: increment,
decrement: decrement
};
// src/main.js
var counter = require('../../lib/counter');
counter.increment();
console.log(counter.counter); // 1