Js模組化開發的理解

WindrunnerMax發表於2020-11-04

Js模組化開發的理解

模組化是一個語言發展的必經之路,其能夠幫助開發者拆分和組織程式碼,隨著前端技術的發展,前端編寫的程式碼量也越來越大,就需要對程式碼有很好的管理,而模組化能夠幫助開發者解決命名衝突、管理依賴、提高程式碼的可讀性、程式碼解耦以及提高程式碼的複用性。

描述

模組化開發其實就是封裝細節,提供使用介面,彼此之間互不影響,每個模組都是實現某一特定的功能,同時也需要避免全域性變數的汙染,最初通過函式實現模組,實際上是利用了函式的區域性作用域來形成模組。

function func1(){
     //...
}

function func2(){
     //...
}

上述的func1func2函式分別形成了兩個模組,需要使用的時候直接呼叫即可,但是這樣無法保證不與其他模組發生變數名衝突,而且模組成員之間看不出直接關係,再之後便有使用物件作為模組,將成員的都放置於物件中。

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

模組化規範

CommonJsAMDCMDES6都是用於模組化定義中使用的規範,其為了規範化模組的引入與處理模組之間的依賴關係以及解決命名衝突問題,並使用模組化方案來使複雜系統分解為程式碼結構更合理,可維護性更高的可管理的模組。

CommonJS

CommonJSNodeJs伺服器端模組的規範,根據這個規範,每個檔案就是一個模組,有自己的作用域。在一個檔案裡面定義的變數、函式、類,都是私有的,對其他檔案不可見。CommonJS規範規定,每個模組內部,module變數代表當前模組。這個變數是一個物件,它的exports屬性是對外的介面。載入某個模組,其實是載入該模組exports屬性。總之,CommonJS規範通過require匯入,module.exportsexports進行匯出。

// 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.exportsexports,則只會匯出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 YESAMD非同步模組定義,全稱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在推廣過程中對模組定義的規範化產出,也是瀏覽器端的模組化非同步解決方案,CMDAMD的區別主要在於:

  • 對於依賴的模組,AMD是提前執行(相對定義的回撥函式, AMD載入器是提前將所有依賴載入並呼叫執行後再執行回撥函式),CMD是延遲執行(相對定義的回撥函式, CMD載入器是將所有依賴載入後執行回撥函式,當執行到需要依賴模組的時候再執行呼叫載入的依賴項並返回到回撥函式中),不過RequireJS2.0開始,也改成可以延遲執行
  • AMD是依賴前置(在定義模組的時候就要宣告其依賴的模組),CMD是依賴就近(只有在用到某個模組的時候再去require——按需載入,即用即返)。
define(function(require,exports,module){
  var a = reuire('require.js');
  a.dosomething();
  return {};
});

ES6

ES6在語言標準的層面上實現了模組的功能,是為了成為瀏覽器和伺服器通用的模組解決方案,ES6標準使用exportexport default來匯出模組,使用import匯入模組。此外在瀏覽器環境中是可以使用require來匯入exportexport default匯出的模組的,但依然建議使用import標準匯入模組。目前ES6模組是靜態的,無法實現按需載入,當然可以使用babel進行解析,也可以使用CommonJSrequire,此外有一份新的規範提案也有可能將動態載入併入標準。
exportexport 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

相關文章