js中的子產品化一:我們知道最常見的子產品化方案有CommonJS、AMD、CMD、ES6,AMD規範一般用于浏覽器,異步的,因為子產品加載是異步的,js解釋是同步的,是以有時候導緻依賴還沒加載完畢,同步的代碼運作結束;CommonJS規範一般用于服務端,同步的,因為在伺服器端所有檔案都存儲在本地的硬碟上,傳輸速率快而且穩定。
1.script标簽引入
最開始的時候,多個script标簽引入js檔案。但是,這種弊端也很明顯,很多個js檔案合并起來,也是相當于一個script,造成變量污染。項目大了,不想變量污染也是很難或者不容易做到,開發和維護成本高。 而且對于标簽的順序,也是需要考慮一陣,還有加載的時候同步,更加是一種災難,幸好後來有了渲染完執行的defer和下載下傳完執行的async,進入新的時代了。
接着,就有各種各樣的動态建立script标簽的方法,最終發展到了上面的幾種方案。
2.AMD與CMD
2.1AMD
異步子產品定義,提供定義子產品及異步加載該子產品依賴的機制。AMD遵循依賴前置,代碼在一旦運作到需要依賴的地方,就馬上知道依賴是什麼。而無需周遊整個函數體找到它的依賴,是以性能有所提升。但是開發者必須先前知道依賴具體有什麼,并且顯式指明依賴,使得開發工作量變大。而且,不能保證子產品加載的時候的順序。 典型代表requirejs。require.js在聲明依賴的子產品時會立刻加載并執行子產品内的代碼。require函數讓你能夠随時去依賴一個子產品,即取得子產品的引用,進而即使子產品沒有作為參數定義,也能夠被使用。他的風格是依賴注入,比如:
/api.js
define('myMoudle',['foo','bar'],function(foo,bar){
//引入了foo和bar,利用foo、bar來做一些事情
return {
baz:function(){return 'api'}
}
});
require(['api'],function(api) {
console.log(api.baz())
})
複制代碼
然後你可以在中間随時引用子產品,但是子產品第一次初始化的時間比較長。這就像開始的時候很拼搏很辛苦,到最後是美滋滋。
2.2CMD
通用子產品定義,提供子產品定義及按需執行子產品。遵循依賴就近,代碼在運作時,最開始的時候是不知道依賴的,需要周遊所有的require關鍵字,找出後面的依賴。一個常見的做法是将function toString後,用正則比對出require關鍵字後面的依賴。CMD 裡,每個 API 都簡單純粹。可以讓浏覽器的子產品代碼像node一樣,因為同步是以引入的順序是能控制的。 對于典型代表seajs,一般是這樣子:
define(function(require,exports,module){
//...很多代碼略過
var a = require('./a');
//要用到a,于是引入了a
//做一些和子產品a有關的事情
對于b.js依賴a.js
//a.js
define(function(require, exports) {
exports.a = function(){//也可以把他暴露出去
// 很多代碼
};
//b.js
define(function(require,exports){
//前面幹了很多事情,突然想要引用a了
var fun = require('./a');
console.log(fun.a()); // 就可以調用到及執行a函數了。
//或者可以use
seajs.use(['a.js'], function(a){
//做一些事情
AMD和CMD對比: AMD 推崇依賴前置、提前執行,CMD推崇依賴就近、延遲執行。
AMD需要先列出清單,後面使用的時候随便使用(依賴前置),異步,特别适合浏覽器環境下使用(底層其實就是動态建立script标簽)。而且API 預設是一個當多個用。
CMD不需要知道依賴是什麼,到了改需要的時候才引入,而且是同步的,就像臨時抱佛腳一樣。
對于用戶端的浏覽器,一說到下載下傳、加載,肯定就是和異步脫不了關系了,注定浏覽器一般用AMD更好了。但是,CMD的api都是有區分的,局部的require和全局的require不一樣。
3.CommonJS與ES6
3.1 ES6
ES6子產品的script标簽有點不同,需要加上type='module'
對于這種标簽都是異步加載,而且是相當于帶上defer屬性的script标簽,不會阻塞頁面,渲染完執行。但是你也可以手動加上defer或者async,實作期望的效果。 ES6子產品的檔案字尾是mjs,通過import引入和export導出。我們一般是這樣子:
//a.mjs
import b from 'b.js'
//b.mjs
export default b
ES6畢竟是ES6,子產品内自帶嚴格模式,而且隻在自身作用域内運作。在ES6子產品内引入其他子產品就要用import引入,暴露也要用export暴露。另外,一個子產品隻會被執行一次。 import是ES6新文法,可靜态分析,提前編譯。他最終會被js引擎編譯,也就是可以實作編譯後就引入了子產品,是以ES6子產品加載是靜态化的,可以在編譯的時候确定子產品的依賴關系以及輸入輸出的變量。ES6可以做到編譯前分析,而CMD和AMD都隻能在運作時确定具體依賴是什麼。
3.2CommonJS
一般服務端的檔案都在本地的硬碟上面。對于客戶,他們用的浏覽器是要從這裡下載下傳檔案的,在服務端一般讀取檔案非常快,是以同步是不會有太大的問題。require的時候,馬上将require的檔案代碼運作
代表就是nodejs了。用得最多的,大概就是:
//app.js
var route = require('./route.js')//讀取控制路由的js檔案
//route.js
var route = {......}
module.exports = route
require 第一次加載腳本就會馬上執行腳本,生成一個對象
差別: CommonJS運作時加載,輸出的是值的拷貝,是一個對象(都是由module.export暴露出去的),可以直接拿去用了,不用再回頭找。是以,當module.export的源檔案裡面一些原始類型值發生變化,require這邊不會随着這個變化而變化的,因為被緩存了。但是有一種正常的操作,寫一個傳回那個值的函數。就像angular裡面$watch數組裡面的每一個對象,舊值是直接寫死,新值是寫一個傳回新值的函數,這樣子就不會寫死。module.export輸出一個取值的函數,調用的時候就可以拿到變化的值。
ES6是編譯時輸出接口,輸出的是值的引用,對外的接口隻是一種靜态的概念,在靜态解釋後已經形成。當腳本運作時,根據這個引用去原本的子產品内取值。是以不存在緩存的情況,import的檔案變了,誰發出import的也是拿到這個變的值。子產品裡面的變量綁定着他所在的子產品。另外,通過import引入的這個變量是隻讀的,試圖進行對他指派将會報錯。