深入理解模組化編程
- 2019 年 10 月 6 日
- 筆記
1.模組化開發規範
JavaScript中所有對象的屬性都是公共的,並沒有什麼明確的方法來表明屬性能否從對象的外部被訪問,而有時候我們並不希望對象的屬性被外界訪問。一種方式方式通過命名約定的形式,比如在變數的前面加下劃線(_)。還有一些其他的方式是屬性完全私有化。
2.為什麼要模組化
在模組化沒有出現之前,我們JavaScript腳本大概是這樣的:
<script src="module1.js"></script> <script src="module2.js"></script> <script src="module3.js"></script> <script src="module4.js"></script> ....
但我們引入多個js文件時,會存在一些問題:
- 把所有的程式碼放到全局環境會引起衝突
- 各個腳本必須按照嚴格的依賴順序放置
- 在大型的項目中,可能會引入很多的js腳本,
script
就會變得很多,並且難以維護。
為了解決這些問題,湧現了各種模組化的方案。
3.模組化的方式
這種方式是創建對象的一種方法,用於創建具有私有屬性的對象。基本思路是使用一個立即執行的函數表達式,返回一個對象。該函數表達式可以包含任意數量的局部變數,這些變數從函數的外部是無法訪問到的。因為返回的對象是在自執行函數內部聲明的,所以對象中定義的方法可以訪問自執行函數內的局部變數,這些方法被具有特權的方法。
var p2 = (function(){ var money = 30000; return { name: 'lisi', sayMoney: function(){ return money; } }; }());
4. CommonJS
我們在前端的js程式碼在沒有模組化之前也能正常執行,但是在伺服器端的js腳本必須要被模組化才能正常工作。所以雖然JavaScript在前端發展了這麼多年,第一個流行的模組化規範卻是由伺服器端的js應用發展起來的。CommonJS規範是由NodeJS發揚光大的。
- 定義模組
在CommonJS規範中,一個單獨的JS文件就是一個模組。每個模組都是一個單獨的作用域,也就是說,在該模組內部定義的變數,無法被其他模組讀取,除非定義為global對象的屬性。
- 模組輸出
模組只有一個出口,module.exports
對象,我們需要把需要輸出的內容放入該模組
- 載入模組
載入模組使用require()
方法,該方法讀取一個文件並執行,返迴文件內部的module.exprots
對象
例如,我們寫了這樣一個文件myModule1.js
:
var name = '無忌'; function sayName(){ console.log(name); }; function sayFullName(firstName){ console.log(firstName + name); }; module.exports = { printName:sayName, printFullName:sayFullName };
我們的模組定義好了,那我們怎樣使用這個模組呢?例如,我們創建了myModule2.js
文件:
var module1 = require('./myModule1.js'); module1.printName(); module1.printFullName('張');
在node環境下,require方法在引入其他模組的時候是同步的,可以輕鬆的控制模組的引入順序,保證了模組之間的依賴順序。但是在瀏覽器中卻不是這樣的,因為我們的<script>
標籤天生非同步,在載入js文件的時候是非同步的,也就意味著不能保證模組之間的正確依賴。
5. AMD規範
AMD即Asynchronous Module Definition,非同步模組定義。它是在瀏覽器端實現模組化開發的規範。由於該規範不是JavaScript原始支援的,使用AMD規範進行開發的時候需要引入第三方的庫函數,也就是鼎鼎大名的RequireJS
。
RequireJS主要解決兩個問題:
- 多個js文件可能有依賴關係,被依賴的文件需要早於依賴它的文件載入到瀏覽器。
- js載入的時候瀏覽器會停止頁面渲染,載入的文件越多,頁面失去響應的時間越長。
- 定義模組
RequireJS定義了一個define
函數,用來定模組。
define(id, [dependencies], factory);
- id:可選參數,用來定義模組的標識,如果沒有提供參數的話,默認為文件的名字。
- dependencies:當前模組依賴的其他模組,數組。
- factory:工廠方法,初始化模組需要執行的函數或對象。如果為函數,它只被執行一次。如果是對象,此對象會作為模組的輸出值。
// amdModule1.js define('amdModule1',[],function(){ console.log('模組一'); var name= '張三'; var money = 1000; var sayName = function(){ return name; } var sayMoney = function(){ return money; } return { sayName:sayName, sayMoney:sayMoney } }) // module2.js define('amdModule2',['amdModule1'],function(amdModule1){ console.log('模組二'); var name = '李四'; var weight = 80; var sayName = function(){ return amdModule1.sayName(); } var sayWeight = function(){ return weight; } return { sayWeight:sayWeight, name:name, sayName:sayName }; })
- 在頁面中載入模組
載入模組需要使用require()
函數。
require([dependencies], function(){});
- dependencies:該參數是一個數組,表示所依賴的模組。
- function:這是一個回調函數,當所依賴的模組都載入成功之後,將調用該回調方法。依賴的模組會以參數的形式傳入該函數,從而在回調函數內部就可以使用這些模組。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script src="require.js"></script> <script type="text/javascript" src="amdModule1.js"></script> <script type="text/javascript" src="amdModule2.js"></script> <script type="text/javascript"> require(['amdModule1','amdModule2'],function(amdModule1,amdModule2){ console.log(amdModule1.sayName()); //張三 console.log(amdModule1.name); //undefined console.log(amdModule2.sayName()); //張三 console.log(amdModule2.sayWeight()); //80 }); </script> </body> </html>
require()
函數在載入依賴的模組時,是非同步載入的,這樣瀏覽器就不會失去響應,回調函數只有在所依賴的全部模組都被被成功載入之後才執行,因此,解決了依賴性的問題。
6. CMD規範
CMD即Common Module Definition通用模組定義。它解決的問題和AMD規範是一樣的,只不過在模組定義方式和模組載入時機上不同。CMD也需要額外的引入第三方的庫文件,SeaJS。
SeaJS推崇一個模組一個文件,遵循統一的寫法:
define(id, dependencies, factory);
因為CMD推崇一個文件一個模組,所以經常使用文件名作為模組的ID;CMD推崇就近原則,所以一般不再define的參數中寫依賴,在factory函數中寫。
- require:我們定義的模組可能會依賴其他模組,這個時候就可使用require()引入依賴。
- exports:等價於module.exports,只是為了方便我們使用
- module.exports:用於存放模組需要暴露的屬性
- 定義模組
// cmdModule1.js define(function(require,exports,module){ var name = '張三'; function sayName(){ return name; } module.exports={ sayName:sayName } }) // cmdModule2.js define(function(require,exports,module){ var cmdModule1 = require('cmdModule1.js'); console.log('模組'); function sayName(){ return cmdModule1.sayName(); } module.exports={ sayName:sayName } })
- 使用模組
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script src="sea.js"></script> <script src="cmdModule2.js"></script> <script src="cmdModule1.js"></script> <script type="text/javascript"> seajs.use(['cmdModule2.js','cmdModule1.js'],function(cmdModule2,cmdModule1){ console.log(cmdModule2.sayName()); console.log(cmdModule1.sayName()); }) </script> </body> </html>
7. AMD和CMD規範的區別
AMD在載入模組完成後會立即執行該模組,所有的模組都載入完成後執行require
方法中的回調函數,執行主邏輯,這樣的效果就是依賴模組的執行順序和書寫順序不一定一致,看網速,誰先下載下來,誰先執行,但是我們的主邏輯一定是在所有的依賴模組都被載入完成後才執行。
CMD載入完某個模組的時候並不執行,只是把它們下載下來而已,在所有的模組下載完成後,當使用require
請求某個模組的時候,才執行對應的模組。