公司項目最近需要將js文件遷移到seajs來進行模塊化管理,由於我以前主要接觸模塊化開發是接觸的AMD規范的requireJS,沒有接觸過CMD規范,而且在實際項目中還沒有用過類似技術。於是,我非常興奮的開始了seajs的學習,正好對模塊化開發仰慕已久,終於有機會大展身手了!
一開始總是有點曲折的,我照着玉伯的github上的教程一步步來,然后發現在我引入jquery的時候,require到的jquery竟然是undefined,經過一番摸索,我發現原來jquery是基於amd規范的,seajs官網的例子之所以能夠成功引入jquery,是因為其jquery是采用cmd規范模塊化之后的產物,而顯然我從百度cdn上拿下來的jquery是沒有經過該處理的。百度了一下,發現jquery想改造成seajs能引入的模塊,有至少兩種方法
1.在jquery源碼外層套上define,將其以cmd的規范模塊化
2.找到jquery最后幾行的&&amd的條件,可以使用ctrl+f搜索amd即可找到,刪掉該條件
顯然,第二種方法更為方便,改造之后,果然jquery能夠正確引入了,不過據其他部門同事的反饋,改造后的jquery模塊在某些情況下會出現莫名其妙的bug,據他說在ios上有bug,由於暫時沒遇到bug,因此暫且還是先將jquery也封裝為模塊
seajs的基本配置還是比較簡單的,首先,在最前面先引入seajs本身,這應該是毋庸置疑的,然后開始配置seajs,這里我遇到一個坑,網上部分教程指出config可以使用data-config來引入,寫在單獨文件里,我愉快的這樣做了,然后一直報錯,發現路徑指向錯誤,糾結了我半天,最后突然看到原來不知道從哪個新版本開始移除了這個屬性,我的天,太逗了
廢話少說,正式開始配置
1 seajs.config({ 2 base:"../sea-modules", 3 alias:{ 4 "jquery":"jquery/jquery.min.js" 5 } 6 });
以下即是seajs常用屬性的用法:
base提供基礎路徑
alias是別名,用於將較長的路徑簡化 以上兩個參數是最基本的參數,實際上還有以下幾個參數
paths:用來統一路徑前綴,適用於較長的外網前綴
preload:預加載部分模塊,貌似已移除
debug:設置為true開啟調試模式,在控制台輸出一些錯誤警告
map:將某個路徑映射到另一個,常用於在線調試,比如spm構建得到的一般有xx.js和xx-debug.js,此時可用map將.js映射為-debug.js,方便在線調試
vars:設置seajs自帶變量,可用{變量名}來獲取,常用於模塊路徑一開始不確定的情況,例如中文或英文,zh-cn或en,該變量是加在路徑上的,src="../{變量名}/main.js"
charset:引用script文件時的charset屬性,默認為utf-8,該屬性可以為函數,具體值為函數返回的值
另外seajs.config函數可以運行多次,多次運行會自動合並配置的參數 配置結束后,開始寫入口函數
seajs.use("../static/main");
這是最基本的入口,use方法用來引入模塊,此處我引入了main.js,也就是入口js 由於seajs是異步加載模塊的,所以這里還可以加入回調函數,傳入一個形參,即可獲取到main模塊,接下來就可以調用main暴露出來的方法 配置完,寫完入口,接下來就是重頭戲,寫模塊了 seajs遵循cmd規范,要求每個模塊需要按該規范風格書寫 即
define(id,dependencies,function(require,exports,module){ //前面兩個參數,一個是當前模塊唯一標志,一個是當前模塊依賴的模塊。正常情況下不必指定這兩個參數,seajs會幫我們自動獲取,第三個factory函數是模塊的工廠函數 //require用於獲取其他模塊,如: var a = require("moduleA"); //此處require內寫的名稱可以是具體路徑,也可以是alias里定義的別名,一般寫的是別名 //通過require語句執行了對應的模塊函數,並返回該模塊的module.exports對象 //注意,除了返回對象外,也執行了該函數,比如,該模塊里如果有一句alert(1)不在exports暴露的方法里,會在require調用的同時直接執行。 a.fn();//獲取到a模塊后,即可調用a模塊暴露出來的方法 })
以下是a模塊,同樣使用cmd規范
define(function(require,exports,module){
var bb="no bb";
exports.fn=function(){
console.log(bb);
};
});
顯然,用腳也能想到控制台會打印 “no bb”, cmd規范里比較重要的概念就是使用exports來暴露屬性或方法, 例如exports.a=3,exports.fnn=function(){}, 這樣其他模塊用require關鍵字獲取到對象的同時,就能使用這些暴露出來的屬性或方法了。
可能有人已經注意到了,我們的factory函數里三個參數,前面兩個已經用到了,require用來獲取模塊,exports用來暴露模塊,那第三個參數呢,有什么作用?
問得好!其實一開始我也很納悶,這個玩意到底是干嘛的,經過一番研究,我大體上了解了這個參數 ,原來require獲取到的模塊實際上最后返回的是module,而調用方法也是通過module.exports獲取到的 exports是module.exports的一個引用,至於為什么要拐個彎,我個人猜測有兩個原因(作為一個初學者大膽的猜測,如果有誤歡迎指正)
1.名字短,寫的爽。好吧,我開個玩笑。
2.避免隨意改動模塊對象,這個才是重點,前面也強調了exports只是一個引用,其指向了module.exports的內存地址 但是引用畢竟是引用,修改引用是不會改動被引用的對象的。
舉個例子說明一下。
module.exports=5;
exports=3;
此時require后返回的值就會是5,而不會是3,這就是引用和本身最大的區別。
關於這兩個的區別有一個新手使用常犯的錯誤。好吧,我沒犯過(得意中~) 某些場景里,我們頻繁使用exports向外提供接口,可能寫了多個exports.xxx=xxx 。
這個時候,初學者可能會想我可以這樣寫
exports={
a:xxx,
b:xxx,
c:xxx
}
想想就激動啊,這樣寫多專業,就好像js面向對象里,給構造函數的prototype拓展方法和屬性時,也會用到這種寫法 。
然后,很不幸的告訴你,這種寫法是錯誤的,至於原因嘛,還是剛剛提到的知識點,exports僅僅是module.exports的一個引用,改變exports的值並不會影響到module.exports。 所以你費盡千辛萬苦簡化的代碼並沒有什么卵用,最后require時引用的module.exports根本沒有像你想的那樣賦值 。
當然,這種寫法的方向是正確的,確實可以簡化代碼,如果需要這樣寫,這里一般有兩種寫法:
方法1:
module.exports={ .... }
這種方法直接給module.exports賦值,一了百了。
方法2:
return{ .... }
利用return返回的內容默認也相當於傳入了module.exports 。
到此為止,我們已經能夠基本使用require來獲取模塊,exports來暴露接口。
但是,還沒結束呢,在我學習的過程中,可不止引入了一個模塊,這個時候,引入多個模塊會有一個小問題。
比如
var a=require("modulessA"); var b=require("modulesB");
我引入了兩個模塊,一個modulesA,一個modulesB,但由於粗心,我寫錯了第一個模塊的名字,此時獲取a模塊那一行會報錯,從而阻塞后續代碼的運行 這樣會造成很不好的影響,要知道seajs的初衷就是盡量0阻塞 。
此處,seajs提供了require.async方法來異步獲取模塊,
var a=require.async("modulesssssA");
此時,雖然該行會報錯,但不會影響后續代碼的執行,這就是異步加載帶來的好處,使用async時還可以傳入回調函數來指定加載完之后執行的邏輯 。
談到異步與同步,突然又想起一個需要注意的地方 ,那就是module.exports的賦值必須是同步完成,而不能放在回調函數里,。
例如
setTimeout(function(){ module.exports=... },0);
此時module.exports會變為 undefined 。
在文章的最后,還提醒大家注意到一個性能問題,那就是seajs模塊化項目之后,如果功能較多,大量的模塊js加載會造成大量請求,這顯然對項目性能是有影響的 。
玉伯本人是推薦是用spm工具來壓縮合並這些模塊,這樣所有的模塊會合並到一個js里,適合項目上線使用 至於spm的具體配置,本人就不詳細講解了,百度一下,你就知道。
聽我啰嗦了半天,希望初學者能對seajs有充分的了解,我本人也是初次接觸,這是我學習了一天之后總結出來的一些基礎知識,適用於剛剛上手的朋友,至於大牛們,還請批評指正,畢竟我的理解也比較淺薄,難免有疏漏和表達不當的地方。
最后還啰嗦一句! seajs和requirejs感覺最大的不同在於seajs是按需加載,用到的時候再加載,而requirejs是提前加載,提前就將用到的模塊寫在一個數組里一開始就加載好, 至於孰優孰劣,在下也不好評價。