隨着前端js代碼復雜度的提高,JavaScript模塊化這個概念便被提出來,前端社區也不斷地實現前端模塊化,直到es6對其進行了規范,下面就介紹JavaScript模塊化。
這篇文章還是希望能給大家一個比較好的思路,即JavaScript模塊化是如何一步一步地發展起來的,並且也會主要對這些模塊化方式做一個簡單的比較。
第一階段:無模塊化
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規范
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等。
注意:因為module.exports本身就是一個對象,所以,我們在導出時可以使用 module.exports = {foo: 'bar'} 也可以使用 module.exports.foo = 'bar'。但是, exports 是 module.exports 的一個引用,或者理解為exports是一個指針,exports指向module.exports,這樣,我們就只能使用 exports.foo = 'bar' 的方式,而不能使用exports = {foo: 'bar'}這種方式,因為exports = {foo: 'bar'}這種方式的使用就會導致exports指向了別的對象,那么這個模塊的輸出就會有問題了。
然后我們就可以在其他模塊中引入這個模塊使用了:
vara = require('./a.js'); console.log(example.x); // 5 console.log(example.addX(1)); // 6
這里的require就會獲取到a.js所暴露的module.exports變量,然后就可以使用其暴露的x和addX了。
優點:
CommonJS規范在服務器端率先完成了JavaScript的模塊化,解決了依賴、全局變量污染的問題,這也是js運行在服務器端的必要條件。
缺點:
這篇文章我們講的主要是瀏覽器端js的模塊化, 由於 CommonJS 是同步加載模塊的,在服務器端,文件都是保存在硬盤上,所以同步加載沒有問題,但是對於瀏覽器端,需要將文件從服務器端請求過來,那么同步加載就不適用了,所以,CommonJS是不適用於瀏覽器端的。
第三階段: AMD規范
之前提到: CommonJS規范加載模塊是同步的,也就是說,只有加載完成,才能執行后面的操作。AMD規范則是非同步加載模塊,允許指定回調函數。由於Node.js主要用於服務器編程,模塊文件一般都已經存在於本地硬盤,所以加載起來比較快,不用考慮非同步加載的方式,所以CommonJS規范比較適用。但是,如果是瀏覽器環境,要從服務器端加載模塊,這時就必須采用非同步模式,因此瀏覽器端一般采用AMD規范。而AMD規范的實現,就是大名鼎鼎的require.js了。
AMD標准中,定義了下面兩個API:
- require([module], callback)
- 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的時候,我們必須要提前加載所有的依賴,然后才可以使用,而不是需要使用時再加載。
優點:
- 適合在瀏覽器環境中異步加載模塊。
- 可以並行加載多個模塊。
缺點:
提高了開發成本,並且不能按需加載,而是必須提前加載所有的依賴。
第四階段:CMD規范
CMD規范是阿里的玉伯提出來的,實現js庫為sea.js。 它和requirejs非常類似,即一個js文件就是一個模塊,但是CMD的加載方式更加優秀,是通過按需加載的方式,而不是必須在模塊開始就加載所有的依賴。如下:
efine(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 關鍵字引入模塊,通過 exprot 關鍵字導出模塊,功能較之於前幾個方案更為強大,也是我們所推崇的,但是由於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()); // }
原創文章,未經允許,不得轉載。