本篇文章給大家分享的是有關(guān)JavaScript模塊化編程的介紹,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
創(chuàng)新互聯(lián)主要從事網(wǎng)站建設(shè)、成都做網(wǎng)站、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)博白,十余年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):13518219792
什么是模塊化?
模塊就是實(shí)現(xiàn)特定功能的一組方法,而模塊化是將模塊的代碼創(chuàng)造自己的作用域,只向外部暴露公開的方法和變量,而這些方法之間高度解耦。
寫 JS 為什么需要模塊化編程?
當(dāng)寫前端還只是處理網(wǎng)頁的一些表單提交,點(diǎn)擊交互的時(shí)候,還沒有強(qiáng)化 JS 模塊化的概念,當(dāng)前端邏輯開始復(fù)雜,交互變得更多,數(shù)據(jù)量越來越龐大時(shí),前端對 JS 模塊化編程的需求就越加強(qiáng)烈。
在很多場景中,我們需要考慮模塊化:
基于以上場景,所以,當(dāng)前 JS 模塊化主要是這幾個(gè)目的:
先給結(jié)論:JS 的模塊化編程經(jīng)歷了幾個(gè)階段:
先給結(jié)論圖:
我們知道,在 ES6 之前,JS 是沒有塊作用域的,私有變量和方法的隔離主要靠函數(shù)作用域,公開變量和方法的隔離主要靠對象的屬性引用。
封裝函數(shù)
在 JS 還沒有模塊化規(guī)范的時(shí)候,將一些通用的、底層的功能抽象出來,獨(dú)立成一個(gè)個(gè)函數(shù)來實(shí)現(xiàn)模塊化:
比方寫一個(gè) utils.js 工具函數(shù)文件
// 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 函數(shù)文件劃分的方式,此時(shí)的公開函數(shù)其實(shí)是掛載到了全局對象 window 下,當(dāng)在別人也想定義一個(gè)叫 add 函數(shù),或者多個(gè) js 文件合并壓縮的時(shí)候,會(huì)存在命名沖突的問題。
掛載到全局變量下:
后來我們想到通過掛載函數(shù)到全局對象字面量下的方式,利用 JAVA 包的概念,希望減輕命名沖突的嚴(yán)重性。
var mathUtils1 = { add: function(x, y) { return x + y; }, } var mathUtils2 = { add: function(x, y, z) { return x + y + z; }, } mathUtils.add(); mathUtils.square();
這種方式仍然創(chuàng)建了全局變量,但如果包的路徑很長,那么到最后引用方法可能就會(huì)以module1.subModule.subSubModule.add
的方式引用代碼了。
IIFE
考慮模塊存在私有變量,于是我們利用IIFE(立即執(zhí)行表達(dá)式)創(chuàng)建閉包來封裝私有變量:
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 時(shí)代,大量使用了模塊模式:
jQuery 的插件必須在 JQuery.js 文件之后 ,文件的加載順序被嚴(yán)格限制住,依賴越多,依賴關(guān)系越混亂,越容易出錯(cuò)。
Nodejs 的出現(xiàn),讓 JavaScript 能夠運(yùn)行在服務(wù)端環(huán)境中,此時(shí)迫切需要建立一個(gè)標(biāo)準(zhǔn)來實(shí)現(xiàn)統(tǒng)一的模塊系統(tǒng),也就是后來的 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 規(guī)定每個(gè)模塊內(nèi)部,module 代表當(dāng)前模塊,這個(gè)模塊是一個(gè)對象,有 id,filename, loaded,parent, children, exports 等屬性,module.exports 屬性表示當(dāng)前模塊對外輸出的接口,其他文件加載該模塊,實(shí)際上就是讀取 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 為每個(gè)模塊提供一個(gè) exports 自由變量,指向 module.exports。這等同在每個(gè)模塊頭部,有一行這樣的命令。
var exports = module.exports;
exports 和 module.exports 共享了同個(gè)引用地址,如果直接對 exports 賦值會(huì)導(dǎo)致兩者不再指向同一個(gè)內(nèi)存地址,但最終不會(huì)對 module.exports 起效。
// module.exports 可以直接賦值 module.exports = 'Hello world'; // exports 不能直接賦值 exports = 'Hello world';
CommonJS 總結(jié):
CommonJS 規(guī)范加載模塊是同步的,用于服務(wù)端,由于 CommonJS 會(huì)在啟動(dòng)時(shí)把內(nèi)置模塊加載到內(nèi)存中,也會(huì)把加載過的模塊放在內(nèi)存中。所以在 Node 環(huán)境中用同步加載的方式不會(huì)有很大問題。
另,CommonJS模塊加載的是輸出值的拷貝。也就是說,外部模塊輸出值變了,當(dāng)前模塊的導(dǎo)入值不會(huì)發(fā)生變化。
CommonJS 規(guī)范的出現(xiàn),使得 JS 模塊化在 NodeJS 環(huán)境中得到了施展機(jī)會(huì)。但 CommonJS 如果應(yīng)用在瀏覽器端,同步加載的機(jī)制會(huì)使得 JS 阻塞 UI 線程,造成頁面卡頓。
利用模塊加載后執(zhí)行回調(diào)的機(jī)制,有了后面的 RequireJS 模塊加載器, 由于加載機(jī)制不同,我們稱這種模塊規(guī)范為 AMD(Asynchromous Module Definition 異步模塊定義)規(guī)范, 異步模塊定義誕生于使用 XHR + eval 的開發(fā)經(jīng)驗(yàn),是 RequireJS 模塊加載器對模塊定義的規(guī)范化產(chǎn)出。
AMD 的模塊寫法:
// 模塊名 utils // 依賴 jQuery, underscore // 模塊導(dǎo)出 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 的特點(diǎn)在于:
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(); }; });
SeaJS 是國內(nèi) JS 大神玉伯開發(fā)的模塊加載器,基于 SeaJS 的模塊機(jī)制,所有 JavaScript 模塊都遵循 CMD(Common Module Definition) 模塊定義規(guī)范.
CMD 模塊的寫法:
// 定義模塊 // utils.js define(function(require, exports, module) { exports.each = function (arr) { // 實(shí)現(xiàn)代碼 }; exports.log = function (str) { // 實(shí)現(xiàn)代碼 }; }); // 輸出模塊 define(function(require, exports, module) { var util = require('./util.js'); var a = require('./a'); //在需要時(shí)申明,依賴就近 a.doSomething(); exports.init = function() { // 實(shí)現(xiàn)代碼 util.log(); }; });
CMD 和 AMD 規(guī)范的區(qū)別:
AMD推崇依賴前置,CMD推崇依賴就近:
AMD 的依賴需要提前定義,加載完后就會(huì)執(zhí)行。
CMD 依賴可以就近書寫,只有在用到某個(gè)模塊的時(shí)候再去執(zhí)行相應(yīng)模塊。
舉個(gè)例子:
// 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 執(zhí)行,打印結(jié)果如下:
Require.js:
先執(zhí)行所有依賴中的代碼
I'm mod1 I'm mod2 I'm main say: I'm mod1 say: I'm mod2
Sea.js:
用到依賴時(shí),再執(zhí)行依賴中的代碼
I'm main I'm mod1 say: I'm mod1 I'm mod2 say: I'm mod2
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 }; });
應(yīng)用 UMD 規(guī)范的 JS 文件其實(shí)就是一個(gè)立即執(zhí)行函數(shù),通過檢驗(yàn) JS 環(huán)境是否支持 CommonJS 或 AMD 再進(jìn)行模塊化定義。
CommonJS 和 AMD 規(guī)范都只能在運(yùn)行時(shí)確定依賴。而 ES6 在語言層面提出了模塊化方案, ES6 module 模塊編譯時(shí)就能確定模塊的依賴關(guān)系,以及輸入和輸出的變量。ES6 模塊化這種加載稱為“編譯時(shí)加載”或者靜態(tài)加載。
寫法:
// math.js // 命名導(dǎo)出 export function add(a, b){ return a + b; } export function sub(a, b){ return a - b; } // 命名導(dǎo)入 import { add, sub } from "./math.js"; add(2, 3); sub(7, 2); // 默認(rèn)導(dǎo)出 export default function foo() { console.log('foo'); } // 默認(rèn)導(dǎo)入 import someModule from "./utils.js";
ES6 模塊的運(yùn)行機(jī)制與 CommonJS 不一樣。JS 引擎對腳本靜態(tài)分析的時(shí)候,遇到模塊加載命令import,就會(huì)生成一個(gè)只讀引用。等到腳本真正執(zhí)行時(shí),再根據(jù)這個(gè)只讀引用,到被加載的那個(gè)模塊里面去取值。原始值變了,import加載的值也會(huì)跟著變。因此,ES6 模塊是動(dòng)態(tài)引用,并且不會(huì)緩存值,模塊里面的變量綁定其所在的模塊。
另,在 webpack 對 ES Module 打包, ES Module 會(huì)編譯成 require/exports 來執(zhí)行的。
JS 的模塊化規(guī)范經(jīng)過了模塊模式、CommonJS、AMD/CMD、ES6 的演進(jìn),利用現(xiàn)在常用的 gulp、webpack 打包工具,非常方便我們編寫模塊化代碼。掌握這幾種模塊化規(guī)范的區(qū)別和聯(lián)系有助于提高代碼的模塊化質(zhì)量,比如,CommonJS 輸出的是值拷貝,ES6 Module 在靜態(tài)代碼解析時(shí)輸出只讀接口,AMD 是異步加載,推崇依賴前置,CMD 是依賴就近,延遲執(zhí)行,在使用到模塊時(shí)才去加載相應(yīng)的依賴。
以上就是JavaScript模塊化編程的介紹,小編相信有部分知識點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。