1. 程式人生 > >Js模組化開發的理解

Js模組化開發的理解

# Js模組化開發的理解 模組化是一個語言發展的必經之路,其能夠幫助開發者拆分和組織程式碼,隨著前端技術的發展,前端編寫的程式碼量也越來越大,就需要對程式碼有很好的管理,而模組化能夠幫助開發者解決命名衝突、管理依賴、提高程式碼的可讀性、程式碼解耦以及提高程式碼的複用性。 ## 描述 模組化開發其實就是封裝細節,提供使用介面,彼此之間互不影響,每個模組都是實現某一特定的功能,同時也需要避免全域性變數的汙染,最初通過函式實現模組,實際上是利用了函式的區域性作用域來形成模組。 ```javascript function func1(){ //... } function func2(){ //... } ``` 上述的`func1`與`func2`函式分別形成了兩個模組,需要使用的時候直接呼叫即可,但是這樣無法保證不與其他模組發生變數名衝突,而且模組成員之間看不出直接關係,再之後便有使用物件作為模組,將成員的都放置於物件中。 ```javascript var nameModule={ name:0, func1:function(){ //... }, func2:function(){ //... } } ``` 在模組化規範形成之前,開發者通常使用`Module`設計模式來解決`Js`全域性作用域的汙染問題。`Module`模式最初被定義為一種在傳統軟體工程中為類提供私有和公有封裝的方法,在`JavaScript`中,`Module`模式使用匿名函式自呼叫構建閉包來封裝,通過自定義暴露行為來區分私有成員和公有成員。 ```javascript 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`進行匯出。 ```javascript // 1.js var a = 1; var b = function(){ console.log(a); } module.exports = { a: a, b: b } /* // 當匯出的模組名與被匯出的成員或方法重名時可以有如下寫法 module.exports = { a, b } */ ``` ```javascript // 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`的內容 ```javascript // 1.js var a = 1; var b = function(){ console.log(a); } exports.a = a; exports.b = b; // exports = { a, b } // 不能這麼寫,這樣就改變了exports的指向為一個新物件而不是module.exports ``` ```javascript // 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`規範。 ```javascript 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的 ``` ### CMD `CMD`通用模組定義,是`SeaJS`在推廣過程中對模組定義的規範化產出,也是瀏覽器端的模組化非同步解決方案,`CMD`和`AMD`的區別主要在於: * 對於依賴的模組,`AMD`是提前執行(相對定義的回撥函式, `AMD`載入器是提前將所有依賴載入並呼叫執行後再執行回撥函式),`CMD`是延遲執行(相對定義的回撥函式, `CMD`載入器是將所有依賴載入後執行回撥函式,當執行到需要依賴模組的時候再執行呼叫載入的依賴項並返回到回撥函式中),不過`RequireJS`從`2.0`開始,也改成可以延遲執行 * ` AMD`是依賴前置(在定義模組的時候就要宣告其依賴的模組),`CMD`是依賴就近(只有在用到某個模組的時候再去`require`——按需載入,即用即返)。 ```javascript 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`則不需要。 ```javascript // 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"; // 將執行模組中的全域性程式碼, 但實際上不匯入任何值。 ``` ```javascript // 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; ``` ```html ES6
``` ## 每日一題 ``` 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/imp