Js模組化開發的理解
模組化是一個語言發展的必經之路,其能夠幫助開發者拆分和組織程式碼,隨著前端技術的發展,前端編寫的程式碼量也越來越大,就需要對程式碼有很好的管理,而模組化能夠幫助開發者解決命名衝突、管理依賴、提高程式碼的可讀性、程式碼解耦以及提高程式碼的複用性。
描述
模組化開發其實就是封裝細節,提供使用介面,彼此之間互不影響,每個模組都是實現某一特定的功能,同時也需要避免全域性變數的汙染,最初通過函式實現模組,實際上是利用了函式的區域性作用域來形成模組。
function func1(){
//...
}
function func2(){
//...
}
上述的func1
與func2
函式分別形成了兩個模組,需要使用的時候直接呼叫即可,但是這樣無法保證不與其他模組發生變數名衝突,而且模組成員之間看不出直接關係,再之後便有使用物件作為模組,將成員的都放置於物件中。
var nameModule={
name:0,
func1:function(){
//...
},
func2:function(){
//...
}
}
在模組化規範形成之前,開發者通常使用Module
設計模式來解決Js
全域性作用域的汙染問題。Module
模式最初被定義為一種在傳統軟體工程中為類提供私有和公有封裝的方法,在JavaScript
中,Module
模式使用匿名函式自呼叫構建閉包來封裝,通過自定義暴露行為來區分私有成員和公有成員。
var nameModule = (function() {
var moduleName = "module"; // private
function setModuleName(name) {
moduleName = name;
}
function getModuleName() {
return moduleName;
}
return {
setModuleName: setModuleName,
getModuleName: getModuleName
}
})();
console.log(nameModule.getModuleName()); // module
nameModule.setModuleName("nameModule");
console.log(nameModule.getModuleName()); // nameModule
模組化規範
CommonJs
、AMD
、CMD
、ES6
都是用於模組化定義中使用的規範,其為了規範化模組的引入與處理模組之間的依賴關係以及解決命名衝突問題,並使用模組化方案來使複雜系統分解為程式碼結構更合理,可維護性更高的可管理的模組。
CommonJS
CommonJS
是NodeJs
伺服器端模組的規範,根據這個規範,每個檔案就是一個模組,有自己的作用域。在一個檔案裡面定義的變數、函式、類,都是私有的,對其他檔案不可見。CommonJS
規範規定,每個模組內部,module
變數代表當前模組。這個變數是一個物件,它的exports
屬性是對外的介面。載入某個模組,其實是載入該模組exports
屬性。總之,CommonJS
規範通過require
匯入,module.exports
與exports
進行匯出。
// 1.js
var a = 1;
var b = function(){
console.log(a);
}
module.exports = {
a: a,
b: b
}
/*
// 當匯出的模組名與被匯出的成員或方法重名時可以有如下寫法
module.exports = {
a,
b
}
*/
// 2.js
var m1 = require("./1.js")
console.log(m1.a); // 1
m1.b(); // 1
也可以使用exports
進行匯出,但一定不要重寫exports
的指向,因為exports
只是一個指標並指向module.exports
的記憶體區域,即exports = module.exports = {}
,重寫exports
則改變了指標指向將導致模組不能匯出,簡單來說exports
就是為寫法提供了一個簡便方案,最後其實都是利用module.exports
匯出。此外若是在一個檔案中同時使用module.exports
與exports
,則只會匯出module.exports
的內容
// 1.js
var a = 1;
var b = function(){
console.log(a);
}
exports.a = a;
exports.b = b;
// exports = { a, b } // 不能這麼寫,這樣就改變了exports的指向為一個新物件而不是module.exports
// 2.js
var m1 = require("./1.js")
console.log(m1.a); // 1
m1.b(); // 1
AMD
AMD
規範不是AMD YES
,AMD
非同步模組定義,全稱Asynchronous Module Definition
規範,是瀏覽器端的模組化解決方案,CommonJS
規範引入模組是同步載入的,這對服務端不是問題,因為其模組都儲存在硬碟上,可以等待同步載入完成,但在瀏覽器中模組是通過網路載入的,若是同步阻塞等待模組載入完成,則可能會出現瀏覽器頁面假死的情況,AMD
採用非同步方式載入模組,模組的載入不影響它後面語句的執行。所有依賴這個模組的語句,都定義在一個回撥函式中,等到載入完成之後,這個回撥函式才會執行,RequireJS
就是實現了AMD
規範。
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// do something
});
define(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
// do something
return {};
});
/**
define和require在依賴處理和回撥執行上都是一樣的,不一樣的地方是define的回撥函式需要有return語句返回模組物件(注意是物件),這樣define定義的模組才能被其他模組引用;require的回撥函式不需要return語句,無法被別的模組引用
*/
// html的<script>標籤也支援非同步載入
// <script src="require.js" defer async="true" ></script> <!-- async屬性表明這個檔案需要非同步載入,避免網頁失去響應。IE不支援這個屬性,只支援defer,所以把defer也寫上。 -->
CMD
CMD
通用模組定義,是SeaJS
在推廣過程中對模組定義的規範化產出,也是瀏覽器端的模組化非同步解決方案,CMD
和AMD
的區別主要在於:
- 對於依賴的模組,
AMD
是提前執行(相對定義的回撥函式,AMD
載入器是提前將所有依賴載入並呼叫執行後再執行回撥函式),CMD
是延遲執行(相對定義的回撥函式,CMD
載入器是將所有依賴載入後執行回撥函式,當執行到需要依賴模組的時候再執行呼叫載入的依賴項並返回到回撥函式中),不過RequireJS
從2.0
開始,也改成可以延遲執行 AMD
是依賴前置(在定義模組的時候就要宣告其依賴的模組),CMD
是依賴就近(只有在用到某個模組的時候再去require
——按需載入,即用即返)。
define(function(require,exports,module){
var a = reuire('require.js');
a.dosomething();
return {};
});
ES6
ES6
在語言標準的層面上實現了模組的功能,是為了成為瀏覽器和伺服器通用的模組解決方案,ES6
標準使用export
與export default
來匯出模組,使用import
匯入模組。此外在瀏覽器環境中是可以使用require
來匯入export
、export default
匯出的模組的,但依然建議使用import
標準匯入模組。目前ES6
模組是靜態的,無法實現按需載入,當然可以使用babel
進行解析,也可以使用CommonJS
的require
,此外有一份新的規範提案也有可能將動態載入併入標準。
export
、export default
主要有以下區別:
export
能按需匯入,export default
不行。export
可以有多個,export default
僅有一個。export
能直接匯出變數表示式,export default
不行。export
方式匯出,在匯入時要加{}
,export default
則不需要。
// name-從將要匯入模組中收到的匯出值的名稱
// member, memberN-從匯出模組,匯入指定名稱的多個成員
// defaultMember-從匯出模組,匯入預設匯出成員
// alias, aliasN-別名,對指定匯入成員進行的重新命名
// module-name-要匯入的模組。是一個檔名
// as-重新命名匯入成員名稱(“識別符號”)
// from-從已經存在的模組、指令碼檔案等匯入
import defaultMember from "module-name";
import * as name from "module-name";
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import defaultMember, { member [ , [...] ] } from "module-name";
import defaultMember, * as name from "module-name";
import "module-name"; // 將執行模組中的全域性程式碼, 但實際上不匯入任何值。
// 1.js
var a = 1;
var b = function(){
console.log(a);
}
var c = 3;
var d = a + c;
var obj = { a,b,c }
export {a,b};
export {c,d};
export default obj;
<!-- 3.html 由於瀏覽器限制,需要啟動一個server服務 -->
<!DOCTYPE html>
<html>
<head>
<title>ES6</title>
</head>
<body>
</body>
<script type="module">
import {a,b} from "./1.js"; // 匯入export
import m1 from "./1.js"; // 不加{}即匯入export default
import {c} from "./1.js"; // 匯入export 按需匯入
console.log(a); // 1
console.log(b); // ƒ (){ console.log(a); }
console.log(m1); // {a: 1, c: 3, b: ƒ}
console.log(c); // 3
</script>
</html>
每日一題
https://github.com/WindrunnerMax/EveryDay
參考
https://zhuanlan.zhihu.com/p/22890374
https://www.jianshu.com/p/80354375e1a5
https://juejin.im/post/6844904120088838157
https://www.cnblogs.com/libin-1/p/7127481.html
https://cloud.tencent.com/developer/article/1436328
https://blog.csdn.net/water_v/article/details/78314672
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import