前言
Javascript不是一種模塊化編程語言,它不支持"類"(class),更遑論"模塊"(module)了,隨着前端發展對
模塊需求越來越大,模塊也是經歷了從最初的簡單模塊寫法到AMD和CMD規范的出現,再到ES6發布,目前已經可以
很方便的在Javascript中使用"類"和"模塊"了。
一、以前的寫法
1、原始寫法
function m1(){
//...
}
function m2(){
//...
}
缺點 :
"污染"了全局變量,無法保證不與其他模塊發生變量名沖突,而且模塊成員之間看不出直接關系。
2、對象寫法
為了解決上面缺點,把模塊寫成一個對象,所有的模塊成員都放到這個對象里面
var module1 = new Object({
_count : 0,
m1 : function (){
//...
},
m2 : function (){
//...
}
});
使用
module1.m1();
存在缺點:
但是,這樣的寫法會暴露所有模塊成員,內部狀態可以被外部改寫。比如,外部代碼可以直接改變內部計數器的值。
module1._count = 5;
3、立即執行函數寫法
立即執行函數(IIFE),可以達到不暴露私有成員的目的。
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
上面的寫法,外部代碼無法讀取內部的_count變量。
console.info(module1._count); //undefined
4、放大模式
如果一個模塊很大,必須分成幾個部分,或者一個模塊需要繼承另一個模塊
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
上面的代碼為module1模塊添加了一個新方法m3(),然后返回新的module1模塊。
5、寬放大模式
在瀏覽器環境中,模塊的各個部分通常都是從網上獲取的,有時無法知道哪個部分會先加載。如果采用上一節的寫法,第一個執行的部分有可能加載一個不存在空對象,這時就要采用"寬放大模式"。
var module1 = ( function (mod){
//...
return mod;
})(window.module1 || {});
與"放大模式"相比,"寬放大模式"就是"立即執行函數"的參數可以是空對象。
6、輸入全局變量
獨立性是模塊的重要特點,模塊內部最好不與程序的其他部分直接交互。
為了在模塊內部調用全局變量,必須顯式地將其他變量輸入模塊
var module1 = (function ($, YAHOO) {
//...
})(jQuery, YAHOO);
上面的module1模塊需要使用jQuery庫和YUI庫,就把這兩個庫(其實是兩個模塊)當作參數輸入module1
好處:
這樣做除了保證模塊的獨立性,還使得模塊之間的依賴關系變得明顯
二、CommonJS、AMD和CMD的出現
談到AMD和CMD,不得不說下CommonJS。2009年,美國程序員Ryan Dahl創造了node.js項目,將javascript語言用於服務器端編程,
由於瀏覽器端網頁還比較簡單 ,對於模塊不是特別依賴,但在服務器端因為要與操作系統和其他應用程序互動,CommonJS
就在這樣的背景下誕生了。
引入第三方模塊並調用方法
var math = require('math');
math.add(2,3); // 5
這里有一個CommonJS模塊使用的例子
//模塊定義 myModule.js
var name = 'Byron';
function printName(){
console.log(name);
}
function printFullName(firstName){
console.log(firstName + name);
}
module.exports = {
printName: printName,
printFullName: printFullName
}
//加載模塊
var myModule = require('./myModule.js');
myModule.printName();
1、AMD規范
想象一下如果把這段代碼放到瀏覽器端 ,math.add(2, 3),在第一行require('math')之后運行,因此必須等math.js加載完成。也就是說,如果加載時間很長,整個應用就會停在那里等
var math = require('math');
math.add(2, 3);
這對服務器端不是一個問題,因為所有的模塊都存放在本地硬盤,可以同步加載完成,等待時間就是硬盤的讀取時間。但是,對於瀏覽器,這卻是一個大問題,因為模塊都放在服務器端,等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於"假死"狀態
因此,瀏覽器端的模塊,不能采用"同步加載"(synchronous),只能采用"異步加載"(asynchronous)
AMD也采用require()語句加載模塊,但是不同於CommonJS,它要求兩個參數
require([module], callback);
上面代碼改寫成AMD形式就是這樣
require(['math'], function (math) {
math.add(2, 3);
});
目前實現AMD規范的有RequireJS和curl.js
RequireJS模塊例子:
// 定義模塊 myModule.js
define('myModule', ['dependency'], function(){
var name = 'Byron';
function printName(){
console.log(name);
}
return {
printName: printName
};
});
// 加載模塊
require(['myModule'], function (my){
my.printName();
});
2、CMD規范
CMD: Common Module Definition通用模塊定義, 由國內發展出來, SeaJS是其典型代表, 即SeaJS是通過瀏覽器對CMD的具體實現
SeaJS模塊例子:
// 定義模塊 myModule.js
define(function(require, exports, module) {
var $ = require('jquery.js');
var foo = require('foo');
var out = foo.bar();
$('div').addClass('active');
module.exports = out;
});
// 加載模塊
seajs.use(['myModule.js'], function(my){
});
3、CommonJS、AMD和CMD區別
- CommonJS是同步的, 主要用於服務器
- AMD和CMD是異步的, 兩者的模塊定義和加載機制稍有不同, 主要用於瀏覽器
- AMD推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊,CMD`推崇就近依賴,只有在用到某個模塊的時候再去require
兩個都是定義的全局define函數來定義模塊, define接收函數function(require, exports, module)保持一致 - CMD是懶加載, 僅在require時才會加載模塊; - AMD是預加載, 在定義模塊時就提前加載好所有依賴
- CMD保留了CommonJS風格
三、ES6的使用
ES6在語言標准的層面上, 實現了模塊功能, 而且實現得相當簡單, 完全可以取代CommonJS和AMD規范, 是瀏覽器和服務器通用的模塊解決方案
ES6模塊例子:
//模塊定義 myModule.js
const name = 'Byron';
function printName(){
console.log(name);
}
function printFullName(firstName){
console.log(firstName + name);
}
const myModule = {
printName: printName,
printFullName: printFullName
};
export myModule;
//加載模塊
import myModule, { printFullName } from './myModule.js';
myModule.printName();
printFullName('Michael');
四、最后再說說browserify和webpack
說到 browserify / webpack ,那還要說到 seajs / requirejs 。這四個都是JS模塊化的方案。其中seajs / require 是一種類型,browserify / webpack 是另一種類型。
1、seajs / require :
是一種在線"編譯"模塊的方案,相當於在頁面上加載一個 CMD/AMD 解釋器。這樣瀏覽器就認識了 define、exports、module 這些東西。也就實現了模塊化。
2、browserify / webpack : 是一個預編譯模塊的方案,相比於上面 ,這個方案更加智能。沒用過browserify,這里以webpack為例。首先,它是預編譯的,不需要在瀏覽器中加載解釋器。另外,你在本地直接寫JS,不管是 AMD / CMD / ES6 風格的模塊化,它都能認識,並且編譯成瀏覽器認識的JS。
參考閱讀:
http://www.ruanyifeng.com/blog/2012/10/javascript_module.html
https://www.jianshu.com/p/5226bd9644b6
http://es6.ruanyifeng.com/#docs/module