一、模塊化的由來
在沒有模塊化思想之前,我們總是將大量的邏輯代碼寫在一起,這樣的代碼雜亂無章,沒有條理性,不便於維護,不利用複用。並且很多代碼重複,邏輯重複。甚至造成全局變量污染,也不方便保護私有數據。
為了解決上面的問題,模塊化的編程思想應運而生。
模塊化的基本思想就是:==閉包自調用函數==
對閉包瞭解不夠的同學,請先查看《 JS閉包全面解析》一文。
二、模塊規範
想要了解模塊化就要先知道JS中3個模塊規範。
JS中的模塊規範(CommonJS,AMD,CMD),如果你聽過模塊化這個東西,那麼你就應該聽過或CommonJS、AMD、CMD這些規範,我也聽過,但之前也真的是聽聽而已。直到最近項目中使用到了才有了一定的理解, 現在就看看吧,這些規範到底是啥東西,怎麼用的。(本文對CommonJS及CMD做一個大概的説明,對AMD中RequireJS做較為全面的講解)
1.CommonJS
CommonJS API定義很多普通應用程序(主要指非瀏覽器的應用)使用的API,也是Node中使用的模塊化解決方案。
2.CMD
CMD:common module define,CMD其實是阿里一位大神編寫的seajs中提出的模塊化解決方案。
==其實CMD可以看成是CommonJS的前端實現==。
在前幾年非常火,不過隨着前端框架的崛起,vue、react、angular都集成了各自的模塊化。並且es6、webpack等都提供了模塊化的解決方案。使得seajs出場機會越來越少,作者也停止了更新。seajs也漸漸退出了歷史的舞台。
3.AMD
AMD:async module define:異步模塊定義。
AMD其實就是requireJS實現的模塊化解決方案,下面我會着重的介紹AMD規範中的requireJS。
鏈接>>>RequireJS中文網
三、RequireJS
1.基本用法
例如在一個電商網站中,購物車和商品的邏輯會在需要場景應用,所以我們就可以將兩者抽出作為模塊開發,使用的時候直接引用,直接上代碼。
首先我們先創建一個cart.js文件
define([],function(){
console.log('cart模塊');
})
然後創建一個product.js文件
define([],function(){
console.log('product模塊');
})
然後在首頁index.html中調用模塊
<!-- 首先在官網下載requirejs源文件,通過script標籤導入 -->
<script src="../js/require.js"></script>
<script>
// 將之前定義好的cart和product模塊導入首頁模塊中
require(["cart","product"],function(){
console.log('這裏是首頁模塊');
})
</script>
2.動態加載模塊&模塊返回值
還是上面的栗子,再加一些代碼
cart.js
define([],function(){
// 將函數作為模塊的返回值
return function(){
console.log('購物車模塊初始化');
}
})
product.js
define([],function(){
// 模塊不僅可以返回函數,也可返回對象
return {
init() {
console.log('商品模塊初始化');
}
}
})
index.html
// 這裏我們給require的回調函數添加形參,接收前面對應模塊的返回值,要與數組順序一致
require(['cart','product'],function(cart,product) {
// 這裏我們不想一進入就加載cart和product模塊,而是等點擊按鈕再去加載模塊
// 這裏也可以理解成按需加載模塊
var btn = document.getElementById('btn1');
btn.onclick(function(){
// 在按鈕的點擊事件中去加載模塊
cart();
product.init();
})
})
==注意==:
- 回調函數中的形參一定要與,數組中導入模塊的順序一致
- 沒有返回值的模塊儘量放到最後導入(數組最後),當然es6中可以寫成
param1,,,param2 - 大部分模塊都是按需加載的
3.入口文件
一般將模塊的入口也定義在一個單獨的js文件中,如main.js。
這樣引用入口文件處就可以簡寫為:
<script data-main="./main" src="../js/require.js"></script>
4.入口文件配置---path
通過在入口文件中的一些配置可以讓我們在使用模塊時更加便捷。
如我們想在模塊中使用jQuery,我們要這樣寫
define([jquery-3.3.1],function($){
})
這裏可能有人不理解為什麼每個模塊引用jq,都要引用jq模塊。因為:
- 防止全局變量污染(zepto:$)
- 使用amd方式在每個模塊導入一下,$就是一個局部變量
設想一下,如果有幾十個模塊,每個模塊都這樣引入jq,如果jq的文件目錄發生改變亦或是jq版本改變,那將會是一個非常大的工程。
這時我們就可以利用path來解決這個問題
main.js
require.config({
path:{
jquery:"lib/jquery-3.3.1", // 文件
bootstrap:"assets/bootstrap/js/bootstrap.min", // 文件
service:"../service" // 文件夾
}
})
當然,不是所有的模塊都需要配置在這裏的,一般來説常用的模塊、文件夾才需要配置。
這樣當需要用到jq的時候,只需要導入入口文件中配置好的jquery即可,後續的任何修改,每個引用的模塊都會同步。
// 指定文件的可以直接導入文件,指定文件夾的可以通過配置的文件夾目錄找到對應文件
define(["jquery","service/xxxxService","bootstrap"],function($,xxxxService){
})
為什麼jq可以像我們編寫的其他模塊一樣被導入使用,是因為jq中已經註冊了amd模塊。雖説jq的設計並不是像我們寫的amd模塊那樣,但是在內部已經做了兼容,許多第三方庫都是這麼兼容amd的,通過源碼可以看到:
define([],function() {
// 定義一個模塊,將jq對象返回,這樣我們在導入模塊後拿到的參數$就是這個jq對象
return jQuery;
})
5.入口文件配置---baseUrl
一個模塊化的項目目錄都會比較複雜,如創建兩個js文件,cart.js、cartDetail.js。存放的目錄為~/js/cart/中,那麼想要在cart模塊中倒入cartDetail模塊就要這樣去寫:
cart.js
define(["js/cart/cartDetail"],function(cartDetail){
})
雖然兩個模塊同處一個文件夾中,但是模塊的導入是根據入口文件所在的路徑去查找的,如果入口文件放在根路徑下,那麼導入模塊的路徑也是根路徑。
利用baseUrl簡化路徑查找:
main.js
require.config({
baseUrl:"js/"
})
改造後我們再導入模塊可以這樣去寫:
cart.js
define(["cart/cartDetail"],function(cartDetail){
})
==注意==:path裏面的配置也是相對於baseUrl的
6.requirejs中的循環依賴
場景:a模塊依賴b模塊,但是b模塊也需要a模塊,如果按常理去寫會造成循環依賴,導致報錯。
- 這時我們要在b模塊中添加require模塊的依賴,然後再添加a的依賴
- ==但是一定不要去通過回調函數形參的形式獲取返回值。==
- 在需要執行a模塊代碼的時候通過require調用。
define(["require","a"],function(require){
require("a")();
})
另外還需要==注意==的一點是:一個模塊被不同模塊引用若干次,但是他們獲取到的都是該模塊同一個引用(閉包數據共享),模塊代碼不會重新執行,節省性能。
7.檢測第三方庫是否支持AMD規範
這個方式也是jQuery中使用的。
if ( typeof define === "function" && define.amd ) {
define([], function() {
return jQuery;
} );
}
四、總結
學習模塊化,重要的不是學習具體的實現,而是學習一種思想。只有真正的領悟了模塊化的思想才能把模塊化更好的應用到開發中,並且在使用其他框架時才能更加得心應手。