摘要:也就是說,外部模塊輸出值變了,當前模塊的導入值不會發生變化。三規范的出現,使得模塊化在環境中得到了施展機會。模塊化這種加載稱為編譯時加載或者靜態加載??偨Y的模塊化規范經過了模塊模式的演進,利用現在常用的打包工具,非常方便我們編寫模塊化代碼。
前言
什么是模塊化?
模塊就是實現特定功能的一組方法,而模塊化是將模塊的代碼創造自己的作用域,只向外部暴露公開的方法和變量,而這些方法之間高度解耦。
寫 JS 為什么需要模塊化編程?
當寫前端還只是處理網頁的一些表單提交,點擊交互的時候,還沒有強化 JS 模塊化的概念,當前端邏輯開始復雜,交互變得更多,數據量越來越龐大時,前端對 JS 模塊化編程的需求就越加強烈。
在很多場景中,我們需要考慮模塊化:
團隊多人協作,需要引用別人的代碼
項目交接,我們在閱讀和重構別人的代碼
代碼審查時,檢驗你的代碼是否規范,是否存在問題
寫完代碼,回顧自己寫的代碼是否美觀:)
不同的環境,環境變量不同
基于以上場景,所以,當前 JS 模塊化主要是這幾個目的:
代碼復用性
功能代碼松耦合
解決命名沖突
代碼可維護性
代碼可閱讀性
先給結論:JS 的模塊化編程經歷了幾個階段:
命名空間形式的代碼封裝
通過立即執行函數(IIFE)創建的命名空間
服務器端運行時 Nodejs 的 CommonJS 規范
將模塊化運行在瀏覽器端的 AMD/CMD 規范
兼容 CMD 和 AMD 的 UMD 規范
通過語言標準支持的 ES Module
先給結論圖:
我們知道,在 ES6 之前,JS 是沒有塊作用域的,私有變量和方法的隔離主要靠函數作用域,公開變量和方法的隔離主要靠對象的屬性引用。
封裝函數
在 JS 還沒有模塊化規范的時候,將一些通用的、底層的功能抽象出來,獨立成一個個函數來實現模塊化:
比方寫一個 utils.js 工具函數文件
// utils.js function add(x, y) { if(typeof x !== "number" || typeof y !== "number") return; return x + y; } function square(x) { if(typeof x !== "number") return; return x * x; }
通過 js 函數文件劃分的方式,此時的公開函數其實是掛載到了全局對象 window 下,當在別人也想定義一個叫 add 函數,或者多個 js 文件合并壓縮的時候,會存在命名沖突的問題。
掛載到全局變量下:
后來我們想到通過掛載函數到全局對象字面量下的方式,利用 JAVA 包的概念,希望減輕命名沖突的嚴重性。
var mathUtils1 = { add: function(x, y) { return x + y; }, } var mathUtils2 = { add: function(x, y, z) { return x + y + z; }, } mathUtils.add(); mathUtils.square();
這種方式仍然創建了全局變量,但如果包的路徑很長,那么到最后引用方法可能就會以module1.subModule.subSubModule.add 的方式引用代碼了。
IIFE
考慮模塊存在私有變量,于是我們利用IIFE(立即執行表達式)創建閉包來封裝私有變量:
var module = (function(){ var count = 0; return { inc: function(){ count += 1; }, dec: function(){ count += -1; } } })() module.inc(); module.dec();
這樣私有變量對于外部來說就是不可訪問的,那如果模塊需要引入其他依賴呢?
var utils = (function ($) { var $body = $("body"); var _private = 0; var foo = function() { ... } var bar = function () { ... } return { foo: foo, bar: bar } })(jQuery);
以上封裝模塊的方式叫作:模塊模式,在 jQuery 時代,大量使用了模塊模式:
jQuery 的插件必須在 JQuery.js 文件之后 ,文件的加載順序被嚴格限制住,依賴越多,依賴關系越混亂,越容易出錯。
二、CommonJSNodejs 的出現,讓 JavaScript 能夠運行在服務端環境中,此時迫切需要建立一個標準來實現統一的模塊系統,也就是后來的 CommonJS。
// math.js exports.add = function(x, y) { return x + y; } // base.js var math = require("./math.js"); math.add(2, 3); // 5 // 引用核心模塊 var http = require("http"); http.createServer(...).listen(3000);
CommonJS 規定每個模塊內部,module 代表當前模塊,這個模塊是一個對象,有 id,filename, loaded,parent, children, exports 等屬性,module.exports 屬性表示當前模塊對外輸出的接口,其他文件加載該模塊,實際上就是讀取 module.exports 變量。
// utils.js // 直接賦值給 module.exports 變量 module.exports = function () { console.log("I"m utils.js module"); } // base.js var util = require("./utils.js") util(); // I"m utils.js module 或者掛載到 module.exports 對象下 module.exports.say = function () { console.log("I"m utils.js module"); } // base.js var util = require("./utils.js") util.say();
為了方便,Node 為每個模塊提供一個 exports 自由變量,指向 module.exports。這等同在每個模塊頭部,有一行這樣的命令。
var exports = module.exports;
exports 和 module.exports 共享了同個引用地址,如果直接對 exports 賦值會導致兩者不再指向同一個內存地址,但最終不會對 module.exports 起效。
// module.exports 可以直接賦值 module.exports = "Hello world"; // exports 不能直接賦值 exports = "Hello world";
CommonJS 總結:
CommonJS 規范加載模塊是同步的,用于服務端,由于 CommonJS 會在啟動時把內置模塊加載到內存中,也會把加載過的模塊放在內存中。所以在 Node 環境中用同步加載的方式不會有很大問題。
另,CommonJS模塊加載的是輸出值的拷貝。也就是說,外部模塊輸出值變了,當前模塊的導入值不會發生變化。
三、AMDCommonJS 規范的出現,使得 JS 模塊化在 NodeJS 環境中得到了施展機會。但 CommonJS 如果應用在瀏覽器端,同步加載的機制會使得 JS 阻塞 UI 線程,造成頁面卡頓。
利用模塊加載后執行回調的機制,有了后面的 RequireJS 模塊加載器, 由于加載機制不同,我們稱這種模塊規范為 AMD(Asynchromous Module Definition 異步模塊定義)規范, 異步模塊定義誕生于使用 XHR + eval 的開發經驗,是 RequireJS 模塊加載器對模塊定義的規范化產出。
AMD 的模塊寫法:
// 模塊名 utils // 依賴 jQuery, underscore // 模塊導出 foo, bar 屬性 // main.js require.config({ baseUrl: "script", paths: { "jquery": "jquery.min", "underscore": "underscore.min", } }); // 定義 utils 模塊,使用 jQuery 模塊 define("utils", ["jQuery", "underscore"], function($, _) { var body = $("body"); var deepClone = _.deepClone({...}); return { foo: "hello", bar: "world" } })
AMD 的特點在于:
延遲加載
依賴前置
AMD 支持兼容 CommonJS 寫法:
define(function (require, exports, module){ var someModule = require("someModule"); var anotherModule = require("anotherModule"); someModule.sayHi(); anotherModule.sayBye(); exports.asplode = function (){ someModule.eat(); anotherModule.play(); }; });四、CMD
SeaJS 是國內 JS 大神玉伯開發的模塊加載器,基于 SeaJS 的模塊機制,所有 JavaScript 模塊都遵循 CMD(Common Module Definition) 模塊定義規范.
CMD 模塊的寫法:
// 定義模塊 // utils.js define(function(require, exports, module) { exports.each = function (arr) { // 實現代碼 }; exports.log = function (str) { // 實現代碼 }; }); // 輸出模塊 define(function(require, exports, module) { var util = require("./util.js"); var a = require("./a"); //在需要時申明,依賴就近 a.doSomething(); exports.init = function() { // 實現代碼 util.log(); }; });
CMD 和 AMD 規范的區別:
AMD推崇依賴前置,CMD推崇依賴就近:
AMD 的依賴需要提前定義,加載完后就會執行。
CMD 依賴可以就近書寫,只有在用到某個模塊的時候再去執行相應模塊。
舉個例子:
// main.js define(function(require, exports, module) { console.log("I"m main"); var mod1 = require("./mod1"); mod1.say(); var mod2 = require("./mod2"); mod2.say(); return { hello: function() { console.log("hello main"); } }; }); // mod1.js define(function() { console.log("I"m mod1"); return { say: function() { console.log("say: I"m mod1"); } }; }); // mod2.js define(function() { console.log("I"m mod2"); return { say: function() { console.log("say: I"m mod2"); } }; });
以上代碼分別用 Require.js 和 Sea.js 執行,打印結果如下:
Require.js:
先執行所有依賴中的代碼
I"m mod1 I"m mod2 I"m main say: I"m mod1 say: I"m mod2
Sea.js:
用到依賴時,再執行依賴中的代碼
I"m main I"m mod1 say: I"m mod1 I"m mod2 say: I"m mod2五、UMD
umd(Universal Module Definition) 是 AMD 和 CommonJS 的兼容性處理,提出了跨平臺的解決方案。
(function (root, factory) { if (typeof exports === "object") { // commonJS module.exports = factory(); } else if (typeof define === "function" && define.amd) { // AMD define(factory); } else { // 掛載到全局 root.eventUtil = factory(); } })(this, function () { function myFunc(){}; return { foo: myFunc }; });
應用 UMD 規范的 JS 文件其實就是一個立即執行函數,通過檢驗 JS 環境是否支持 CommonJS 或 AMD 再進行模塊化定義。
六、ES6 ModuleCommonJS 和 AMD 規范都只能在運行時確定依賴。而 ES6 在語言層面提出了模塊化方案, ES6 module 模塊編譯時就能確定模塊的依賴關系,以及輸入和輸出的變量。ES6 模塊化這種加載稱為“編譯時加載”或者靜態加載。
寫法:
// math.js // 命名導出 export function add(a, b){ return a + b; } export function sub(a, b){ return a - b; } // 命名導入 import { add, sub } from "./math.js"; add(2, 3); sub(7, 2); // 默認導出 export default function foo() { console.log("foo"); } // 默認導入 import someModule from "./utils.js";
ES6 模塊的運行機制與 CommonJS 不一樣。JS 引擎對腳本靜態分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊里面去取值。原始值變了,import加載的值也會跟著變。因此,ES6 模塊是動態引用,并且不會緩存值,模塊里面的變量綁定其所在的模塊。
另,在 webpack 對 ES Module 打包, ES Module 會編譯成 require/exports 來執行的。
總結JS 的模塊化規范經過了模塊模式、CommonJS、AMD/CMD、ES6 的演進,利用現在常用的 gulp、webpack 打包工具,非常方便我們編寫模塊化代碼。掌握這幾種模塊化規范的區別和聯系有助于提高代碼的模塊化質量,比如,CommonJS 輸出的是值拷貝,ES6 Module 在靜態代碼解析時輸出只讀接口,AMD 是異步加載,推崇依賴前置,CMD 是依賴就近,延遲執行,在使用到模塊時才去加載相應的依賴。
@Starbucks 2019/03/10
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109063.html
摘要:所有依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成之后,這個回調函數才會運行。 1.模塊的寫法 模塊化編程一般都有這么幾個過渡過程,如下描述。 原始方法 function m1(){ //... } function m2(){ //... } 上面的函數m1()和m2(),組成一個模塊。使用的時候,直接調用就行了。 這種做法的缺點很明顯:污染了全局變量,無法保證不與...
摘要:好棒,應該可以滿足絕大部分公司的變態需求了額。。可以在回調函數中調用其方法。。等下會大幅度減少滴。。。。百度搜索到官網點擊下載對應著自己電腦的版本。??勺詈蟮恼埱笫沁@樣的由此可見,。 序言 -# 公司大了,業務多了,前端代碼量也逐漸增大,我們漸漸的依賴js實現的交互越來越多,長期以來會導致我們的代碼維護越來越困難,所以依賴的插件也越來越多。。比如這樣頁面中有大量的js外鏈引入。。 ...
摘要:模塊化編程首先,我想說說模塊化編程這個概念當我不清楚這個概念的時候,其實說什么模塊化編程多好多好都是懵逼的而我一直不覺得有多好,其實也是因為我從開始寫,就一直都在模塊化編程啊我們寫一個文件然后我們在文件中引入然后調用方法哈哈這樣已經是模塊化 模塊化編程 首先,我想說說模塊化編程這個概念當我不清楚這個概念的時候,其實說什么模塊化編程多好多好都是懵逼的而我一直不覺得有多好,其實也是因為我從...
摘要:面向對象三大特征繼承性多態性封裝性接口。第五階段封裝一個屬于自己的框架框架封裝基礎事件流冒泡捕獲事件對象事件框架選擇框架。核心模塊和對象全局對象,,,事件驅動,事件發射器加密解密,路徑操作,序列化和反序列化文件流操作服務端與客戶端。 第一階段: HTML+CSS:HTML進階、CSS進階、div+css布局、HTML+css整站開發、 JavaScript基礎:Js基礎教程、js內置對...
摘要:面向對象三大特征繼承性多態性封裝性接口。第五階段封裝一個屬于自己的框架框架封裝基礎事件流冒泡捕獲事件對象事件框架選擇框架。核心模塊和對象全局對象,,,事件驅動,事件發射器加密解密,路徑操作,序列化和反序列化文件流操作服務端與客戶端。 第一階段: HTML+CSS:HTML進階、CSS進階、div+css布局、HTML+css整站開發、 JavaScript基礎:Js基礎教程、js內置對...
閱讀 2459·2021-11-22 09:34
閱讀 3066·2021-10-25 09:43
閱讀 1981·2021-10-11 10:59
閱讀 3381·2021-09-22 15:13
閱讀 2330·2021-09-04 16:40
閱讀 423·2019-08-30 15:53
閱讀 3189·2019-08-30 11:13
閱讀 2607·2019-08-29 17:30