經常在工作中使用
define(['./modulename'],function(modulename){})
,require(['modulename'],function(modulename){})
,module.exports=modulename
,import modulename from './modulename'
,export default function(){}
這些模塊但卻很少去了解其由來,趁現在有點時間去看看
一、無模塊化(春秋戰國亂世爭雄)
JavaScript最初的作用僅僅是驗證表單,后來會添加一些動畫,但是這些js代碼很多在一個文件中就可以完成了,所以,我們只需要在html文件中添加一個script標簽。
后來,隨著前端復雜度提高,為了能夠提高項目代碼的可讀性、可擴展性等,我們的js文件逐漸多了起來,不再是一個js文件就可以解決的了,而是把每一個js文件當做一個模塊。那么,這時的js引入方式是怎樣的呢?大概是下面這樣:
<script src="jquery.js"></script>
<script src="jquery_scroller.js"></script>
<script src="main.js"></script>
<script src="other1.js"></script>
<script src="other2.js"></script>
<script src="other3.js"></script>
即簡單的將所有的js文件統統放在一起。但是這些文件的順序還不能出錯,比如jquery需要先引入,才能引入jquery插件,才能在其他的文件中使用jquery。
- 優點
相比于使用一個js文件,這種多個js文件實現最簡單的模塊化的思想是進步的。 - 缺點
污染全局作用域。 因為每一個模塊都是暴露在全局的,簡單的使用,會導致全局變量命名沖突,當然,我們也可以使用命名空間的方式來解決。
對于大型項目,各種js很多,開發人員必須手動解決模塊和代碼庫的依賴關系,后期維護成本較高。
依賴關系不明顯,不利于維護。 比如main.js需要使用jquery,但是,從上面的文件中,我們是看不出來的,如果jquery忘記了,那么就會報錯。
所謂亂世出英雄,歷經了多年征戰,慢慢催生出了一幫幫英雄豪杰(CommonJS ---> AMD ---> CMD)
- 共同點
- 都是使用字符串命名方式,讓模塊作用域只存在于當前模塊作用域內,解決了命名空間的問題,且遵循一個模塊代表一個文件的理念,將一個js系統地“分割“成多個模塊,實現模塊化系統。
- AMD和CMD都是向著CommonJS致敬的,都有CommonJS的身影。
- 不同點
CommonJS起初為了解決,服務端的代碼,把代碼全都擠在一個文件內,從而導致文件復雜臃腫的,瀏覽器加載
該JS文件,產生卡死;全局作用域污染等問題,而應運而生的模塊化開發理念。而且其加載模塊的方式屬于同步的,
需要遵循“先加載,停頓,再執行“的順序來執行代碼,因此很受網速限制。AMD規范,雖然延續了CommonJS的理念,模塊化開發,但不同的是,AMD遵循的是異步加載模塊的規范。其加載依賴模塊方式是屬于依賴前置,即先加載需要的依賴模塊,再執行回調函數,大大提高了效率。
CMD規范,是由國內前端大神,玉伯,編寫的一個js庫 --- sea.js,在推過過程,提出的一個基于CommonJS的新
規范---CMD。該規范與AMD類似,寫法也類似。但不同的是,CMD遵循著依賴后置的理念。即AMD是一次性加載完該模塊所需要的所有模塊,再執行回調。而CMD是按需加載,即需要用到的時候,才去加載對應模塊。
- 雖說AMD和CMD代表分別是requireJS和seaJS,但要清楚一點,前面2個是規范,后面2個是庫,只是模塊加載器。
二、commonJS規范
CommonJS就是一個JavaScript模塊化的規范,該規范最初是用在服務器端的node的,前端的webpack也是對CommonJS原生支持的。
根據這個規范,每一個文件就是一個模塊,其內部定義的變量是屬于這個模塊的,不會對外暴露,也就是說不會污染全局變量。
CommonJS的核心思想就是通過 require 方法來同步加載所要依賴的其他模塊,然后通過 exports 或者 module.exports 來導出需要暴露的接口。如下所示:
// a.js
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
這里的a.js就是一個CommonJS規范的模塊了。 這里的module就代表了這個模塊,module的exports屬性就是對外暴露
的接口,可以對外導出外部可以訪問的變量,比如這里的x和addX。
exports 是對 module.exports 的引用。比如我們可以認為在一個模塊的頂部有這句代碼:
exports = module.exports
所以,我們不能直接給exports賦值,比如number、function等。
然后我們就可以在其他模塊中引入這個模塊使用了:
var example = require('./a.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6
這里的require就會獲取到a.js所暴露的module.exports變量,然后就可以使用其暴露的x和addX了。
- 優點
模塊化,遵循一個模塊一個文件的原則。
耦合低,不容易導致全局變量的污染,因為每個模塊都相當于閉包,都存在于自己的命名空間模塊內的作用域。因此,外部無法訪問內部私有變量。
單一原則。每個模塊內部都包含一個自執行函數,而這里提供2個變量,一個exports,一個module,但是最后輸出的值,是在module.exports。
統一性,都使用module.exorts或exports來導出模塊,然后使用require()函數來導入模塊(同步)。
高度復用,可以將別人的項目內某個功能之間導入使用。(前提是用module.exports或exports導出)。
<font color=green>其實commonJS用于nodejs場景最多,也得利于node的興起而廣為人知</font>
- 缺點
不兼容瀏覽器,不能直接使用。因為瀏覽器不支持global,module,require,exports這四個全局變量。
因為期初CommonJS的出現是應用在服務端,而且它的加載方式屬于同步的,注意,是加載時是同步的。因此,編碼時必須先去加載模塊,再執行,這樣會影響后面的代碼的執行時間,形成阻塞。
三、AMD規范
官方對于AMD 的介紹
The Asynchronous Module Definition (AMD) API specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded. This is particularly well suited for the browser environment where synchronous loading of modules incurs performance, usability, debugging, and cross-domain access problems.
之前提到: CommonJS規范加載模塊是同步的,也就是說,只有加載完成,才能執行后面的操作。AMD規范則是非同
步加載模塊,允許指定回調函數。由于Node.js主要用于服務器編程,模塊文件一般都已經存在于本地硬盤,所以加載
起來比較快,不用考慮非同步加載的方式,所以CommonJS規范比較適用。但是,如果是瀏覽器環境,要從服務器端
加載模塊,這時就必須采用非同步模式,因此瀏覽器端一般采用AMD規范。而AMD規范的實現,就是大名鼎鼎的
require.js了。
AMD標準中,定義了下面兩個API:
1.require([module], callback)
2. define(id, [depends], callback)
即通過define來定義一個模塊,然后使用require來加載一個模塊。 并且,require還支持CommonJS的模塊導出方式。
定義alert模塊:
define(function () {
var alertName = function (str) {
alert("I am " + str);
}
var alertAge = function (num) {
alert("I am " + num + " years old");
}
return {
alertName: alertName,
alertAge: alertAge
};
});
引入模塊:
require(['alert'], function (alert) {
alert.alertName('JohnZhu');
alert.alertAge(21);
});
但是,在使用require.js的時候,我們必須要提前加載所有的依賴,然后才可以使用,而不是需要使用時再加載。
- 優點
模塊化,遵循CommonJS理念,一個文件即模塊。
低耦合,由于采用命名空間內作用域有效,所有外部無法訪問私有變量。
- 統一性,都由全局變量define函數(導出)定義模塊,全局變量require函數導入模塊。
- 高效性,由于采用的是異步加載的方式來加載模塊,加載方法和CommonJS “一樣“,都是使用require來加載模塊。
但是AMD的require有點不一樣,AMD的require導入模塊時是異步的,而且語法是:require([依賴1,依賴2...],function()
{})。也就是說,要先加載完所有依賴,才執行回調函數,而該回調回調函數的參數,也必須嚴格按照數組內的模塊順
序來作為參數,回調函數內都是依賴于這些模塊的邏輯代碼。那些和這些依賴無關的,可以寫在外面,在加載依賴
時,是不會影響接下來的代碼的。這也就對應了:異步加載,是不會影響后面的代碼的運行的。
- 缺點
關系前置,我個人稱為 “依賴前置“ 。可能由于設計思想的原因,AMD雖說是異步加載模塊,并且不能按需加載,而是必須提前加載所有的依賴,再執行回調。
四、 CMD規范
CMD規范是阿里的玉伯提出來的,實現js庫為sea.js。 它和requirejs非常類似,即一個js文件就是一個模塊,但是CMD的加載方式更加優秀,是通過按需加載的方式,而不是必須在模塊開始就加載所有的依賴。如下:
define(function(require, exports, module) {
var $ = require('jquery');
var Spinning = require('./spinning');
exports.doSomething = ...
module.exports = ...
})
優點
同樣實現了瀏覽器端的模塊化加載。
可以按需加載,依賴就近。缺點
依賴SPM打包,模塊的加載邏輯偏重。
其實,這時我們就可以看出AMD和CMD的區別了,前者是對于依賴的模塊提前執行,而后者是延遲執行。 前者推崇依賴前置,而后者推崇依賴就近,即只在需要用到某個模塊的時候再require。 如下:
// AMD
define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好
a.doSomething()
// 此處略去 100 行
b.doSomething()
...
});
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此處略去 100 行
var b = require('./b')
// 依賴可以就近書寫
b.doSomething()
// ...
});
五、ES6模塊化(秦國一統六國)
之前的幾種模塊化方案都是前端社區自己實現的,只是得到了大家的認可和廣泛使用,而ES6的模塊化方案是真正的規范。 在ES6中,我們可以使用 import 關鍵字引入模塊,通過 export 關鍵字導出模塊,功能較之于前幾個方案更為強大,也是我們所推崇的,但是由于ES6目前無法在瀏覽器中執行,所以,我們只能通過babel將不被支持的import編譯為當前受到廣泛支持的 require。
雖然目前import和require的區別不大,但是還是推薦使用使用es6,因為未來es6必定是主流,對于代碼的遷移成本還是非常容易的。 如:
import store from '../store/index'
import {mapState, mapMutations, mapActions} from 'vuex'
import axios from '../assets/js/request'
import util from '../utils/js/util.js'
export default {
created () {
this.getClassify();
this.RESET_VALUE();
console.log('created' ,new Date().getTime());
}