摘要:二模塊化規(guī)范概述應(yīng)用由模塊組成,采用模塊規(guī)范。模塊化語(yǔ)法命令用于規(guī)定模塊的對(duì)外接口,命令用于輸入其他模塊提供的功能。
前言
在JavaScript發(fā)展初期就是為了實(shí)現(xiàn)簡(jiǎn)單的頁(yè)面交互邏輯,寥寥數(shù)語(yǔ)即可;如今CPU、瀏覽器性能得到了極大的提升,很多頁(yè)面邏輯遷移到了客戶(hù)端(表單驗(yàn)證等),隨著web2.0時(shí)代的到來(lái),Ajax技術(shù)得到廣泛應(yīng)用,jQuery等前端庫(kù)層出不窮,前端代碼日益膨脹,此時(shí)在JS方面就會(huì)考慮使用模塊化規(guī)范去管理。
本文內(nèi)容主要有理解模塊化,為什么要模塊化,模塊化的優(yōu)缺點(diǎn)以及模塊化規(guī)范,并且介紹下開(kāi)發(fā)中最流行的CommonJS, AMD, ES6、CMD規(guī)范。本文試圖站在小白的角度,用通俗易懂的筆調(diào)介紹這些枯燥無(wú)味的概念,希望諸君閱讀后,對(duì)模塊化編程有個(gè)全新的認(rèn)識(shí)和理解!
建議下載本文源代碼,自己動(dòng)手敲一遍,請(qǐng)猛戳GitHub個(gè)人博客
一、模塊化的理解 1.什么是模塊?將一個(gè)復(fù)雜的程序依據(jù)一定的規(guī)則(規(guī)范)封裝成幾個(gè)塊(文件), 并進(jìn)行組合在一起
塊的內(nèi)部數(shù)據(jù)與實(shí)現(xiàn)是私有的, 只是向外部暴露一些接口(方法)與外部其它模塊通信
2.模塊化的進(jìn)化過(guò)程
全局function模式 : 將不同的功能封裝成不同的全局函數(shù)
編碼: 將不同的功能封裝成不同的全局函數(shù)
問(wèn)題: 污染全局命名空間, 容易引起命名沖突或數(shù)據(jù)不安全,而且模塊成員之間看不出直接關(guān)系
function m1(){ //... } function m2(){ //... }
namespace模式 : 簡(jiǎn)單對(duì)象封裝
作用: 減少了全局變量,解決命名沖突
問(wèn)題: 數(shù)據(jù)不安全(外部可以直接修改模塊內(nèi)部的數(shù)據(jù))
let myModule = { data: "www.baidu.com", foo() { console.log(`foo() ${this.data}`) }, bar() { console.log(`bar() ${this.data}`) } } myModule.data = "other data" //能直接修改模塊內(nèi)部的數(shù)據(jù) myModule.foo() // foo() other data
這樣的寫(xiě)法會(huì)暴露所有模塊成員,內(nèi)部狀態(tài)可以被外部改寫(xiě)。
IIFE模式:匿名函數(shù)自調(diào)用(閉包)
作用: 數(shù)據(jù)是私有的, 外部只能通過(guò)暴露的方法操作
編碼: 將數(shù)據(jù)和行為封裝到一個(gè)函數(shù)內(nèi)部, 通過(guò)給window添加屬性來(lái)向外暴露接口
問(wèn)題: 如果當(dāng)前這個(gè)模塊依賴(lài)另一個(gè)模塊怎么辦?
// index.html文件
// module.js文件 (function(window) { let data = "www.baidu.com" //操作數(shù)據(jù)的函數(shù) function foo() { //用于暴露有函數(shù) console.log(`foo() ${data}`) } function bar() { //用于暴露有函數(shù) console.log(`bar() ${data}`) otherFun() //內(nèi)部調(diào)用 } function otherFun() { //內(nèi)部私有的函數(shù) console.log("otherFun()") } //暴露行為 window.myModule = { foo, bar } //ES6寫(xiě)法 })(window)
最后得到的結(jié)果:
IIFE模式增強(qiáng) : 引入依賴(lài)
這就是現(xiàn)代模塊實(shí)現(xiàn)的基石
// module.js文件 (function(window, $) { let data = "www.baidu.com" //操作數(shù)據(jù)的函數(shù) function foo() { //用于暴露有函數(shù) console.log(`foo() ${data}`) $("body").css("background", "red") } function bar() { //用于暴露有函數(shù) console.log(`bar() ${data}`) otherFun() //內(nèi)部調(diào)用 } function otherFun() { //內(nèi)部私有的函數(shù) console.log("otherFun()") } //暴露行為 window.myModule = { foo, bar } })(window, jQuery)
// index.html文件
上例子通過(guò)jquery方法將頁(yè)面的背景顏色改成紅色,所以必須先引入jQuery庫(kù),就把這個(gè)庫(kù)當(dāng)作參數(shù)傳入。這樣做除了保證模塊的獨(dú)立性,還使得模塊之間的依賴(lài)關(guān)系變得明顯。
3. 模塊化的好處避免命名沖突(減少命名空間污染)
更好的分離, 按需加載
更高復(fù)用性
高可維護(hù)性
4. 引入多個(gè) 2.AMDCommonJS規(guī)范加載模塊是同步的,也就是說(shuō),只有加載完成,才能執(zhí)行后面的操作。AMD規(guī)范則是非同步加載模塊,允許指定回調(diào)函數(shù)。由于Node.js主要用于服務(wù)器編程,模塊文件一般都已經(jīng)存在于本地硬盤(pán),所以加載起來(lái)比較快,不用考慮非同步加載的方式,所以CommonJS規(guī)范比較適用。但是,如果是瀏覽器環(huán)境,要從服務(wù)器端加載模塊,這時(shí)就必須采用非同步模式,因此瀏覽器端一般采用AMD規(guī)范。此外AMD規(guī)范比CommonJS規(guī)范在瀏覽器端實(shí)現(xiàn)要來(lái)著早。
(1)AMD規(guī)范基本語(yǔ)法定義暴露模塊:
//定義沒(méi)有依賴(lài)的模塊 define(function(){ return 模塊 })
//定義有依賴(lài)的模塊 define(["module1", "module2"], function(m1, m2){ return 模塊 })
引入使用模塊:
require(["module1", "module2"], function(m1, m2){ 使用m1/m2 })(2)未使用AMD規(guī)范與使用require.js
通過(guò)比較兩者的實(shí)現(xiàn)方法,來(lái)說(shuō)明使用AMD規(guī)范的好處。
未使用AMD規(guī)范
// dataService.js文件 (function (window) { let msg = "www.baidu.com" function getMsg() { return msg.toUpperCase() } window.dataService = {getMsg} })(window)
// alerter.js文件 (function (window, dataService) { let name = "Tom" function showMsg() { alert(dataService.getMsg() + ", " + name) } window.alerter = {showMsg} })(window, dataService)
// main.js文件 (function (alerter) { alerter.showMsg() })(alerter)
// index.html文件Modular Demo 1: 未使用AMD(require.js)
最后得到如下結(jié)果:
這種方式缺點(diǎn)很明顯:首先會(huì)發(fā)送多個(gè)請(qǐng)求,其次引入的js文件順序不能搞錯(cuò),否則會(huì)報(bào)錯(cuò)!
使用require.js
RequireJS是一個(gè)工具庫(kù),主要用于客戶(hù)端的模塊管理。它的模塊管理遵守AMD規(guī)范,RequireJS的基本思想是,通過(guò)define方法,將代碼定義為模塊;通過(guò)require方法,實(shí)現(xiàn)代碼的模塊加載。
接下來(lái)介紹AMD規(guī)范在瀏覽器實(shí)現(xiàn)的步驟:
官網(wǎng): http://www.requirejs.cn/
github : https://github.com/requirejs/requirejs
然后將require.js導(dǎo)入項(xiàng)目: js/libs/require.js
②創(chuàng)建項(xiàng)目結(jié)構(gòu)|-js |-libs |-require.js |-modules |-alerter.js |-dataService.js |-main.js |-index.html③定義require.js的模塊代碼
// dataService.js文件 // 定義沒(méi)有依賴(lài)的模塊 define(function() { let msg = "www.baidu.com" function getMsg() { return msg.toUpperCase() } return { getMsg } // 暴露模塊 })
//alerter.js文件 // 定義有依賴(lài)的模塊 define(["dataService"], function(dataService) { let name = "Tom" function showMsg() { alert(dataService.getMsg() + ", " + name) } // 暴露模塊 return { showMsg } })
// main.js文件 (function() { require.config({ baseUrl: "js/", //基本路徑 出發(fā)點(diǎn)在根目錄下 paths: { //映射: 模塊標(biāo)識(shí)名: 路徑 alerter: "./modules/alerter", //此處不能寫(xiě)成alerter.js,會(huì)報(bào)錯(cuò) dataService: "./modules/dataService" } }) require(["alerter"], function(alerter) { alerter.showMsg() }) })()
// index.html文件④頁(yè)面引入require.js模塊:Modular Demo
在index.html引入
此外在項(xiàng)目中如何引入第三方庫(kù)?只需在上面代碼的基礎(chǔ)稍作修改:
// alerter.js文件 define(["dataService", "jquery"], function(dataService, $) { let name = "Tom" function showMsg() { alert(dataService.getMsg() + ", " + name) } $("body").css("background", "green") // 暴露模塊 return { showMsg } })
// main.js文件 (function() { require.config({ baseUrl: "js/", //基本路徑 出發(fā)點(diǎn)在根目錄下 paths: { //自定義模塊 alerter: "./modules/alerter", //此處不能寫(xiě)成alerter.js,會(huì)報(bào)錯(cuò) dataService: "./modules/dataService", // 第三方庫(kù)模塊 jquery: "./libs/jquery-1.10.1" //注意:寫(xiě)成jQuery會(huì)報(bào)錯(cuò) } }) require(["alerter"], function(alerter) { alerter.showMsg() }) })()
上例是在alerter.js文件中引入jQuery第三方庫(kù),main.js文件也要有相應(yīng)的路徑配置。
小結(jié):通過(guò)兩者的比較,可以得出AMD模塊定義的方法非常清晰,不會(huì)污染全局環(huán)境,能夠清楚地顯示依賴(lài)關(guān)系。AMD模式可以用于瀏覽器環(huán)境,并且允許非同步加載模塊,也可以根據(jù)需要?jiǎng)討B(tài)加載模塊。
CMD規(guī)范專(zhuān)門(mén)用于瀏覽器端,模塊的加載是異步的,模塊使用時(shí)才會(huì)加載執(zhí)行。CMD規(guī)范整合了CommonJS和AMD規(guī)范的特點(diǎn)。在 Sea.js 中,所有 JavaScript 模塊都遵循 CMD模塊定義規(guī)范。
(1)CMD規(guī)范基本語(yǔ)法定義暴露模塊:
//定義沒(méi)有依賴(lài)的模塊 define(function(require, exports, module){ exports.xxx = value module.exports = value })
//定義有依賴(lài)的模塊 define(function(require, exports, module){ //引入依賴(lài)模塊(同步) var module2 = require("./module2") //引入依賴(lài)模塊(異步) require.async("./module3", function (m3) { }) //暴露模塊 exports.xxx = value })
引入使用模塊:
define(function (require) { var m1 = require("./module1") var m4 = require("./module4") m1.show() m4.show() })(2)sea.js簡(jiǎn)單使用教程 ①下載sea.js, 并引入
官網(wǎng): http://seajs.org/
github : https://github.com/seajs/seajs
然后將sea.js導(dǎo)入項(xiàng)目: js/libs/sea.js
②創(chuàng)建項(xiàng)目結(jié)構(gòu)|-js |-libs |-sea.js |-modules |-module1.js |-module2.js |-module3.js |-module4.js |-main.js |-index.html③定義sea.js的模塊代碼
// module1.js文件 define(function (require, exports, module) { //內(nèi)部變量數(shù)據(jù) var data = "atguigu.com" //內(nèi)部函數(shù) function show() { console.log("module1 show() " + data) } //向外暴露 exports.show = show })
// module2.js文件 define(function (require, exports, module) { module.exports = { msg: "I Will Back" } })
// module3.js文件 define(function(require, exports, module) { const API_KEY = "abc123" exports.API_KEY = API_KEY })
// module4.js文件 define(function (require, exports, module) { //引入依賴(lài)模塊(同步) var module2 = require("./module2") function show() { console.log("module4 show() " + module2.msg) } exports.show = show //引入依賴(lài)模塊(異步) require.async("./module3", function (m3) { console.log("異步引入依賴(lài)模塊3 " + m3.API_KEY) }) })
// main.js文件 define(function (require) { var m1 = require("./module1") var m4 = require("./module4") m1.show() m4.show() })④在index.html中引入
最后得到結(jié)果如下:
4.ES6模塊化ES6 模塊的設(shè)計(jì)思想是盡量的靜態(tài)化,使得編譯時(shí)就能確定模塊的依賴(lài)關(guān)系,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運(yùn)行時(shí)確定這些東西。比如,CommonJS 模塊就是對(duì)象,輸入時(shí)必須查找對(duì)象屬性。
(1)ES6模塊化語(yǔ)法export命令用于規(guī)定模塊的對(duì)外接口,import命令用于輸入其他模塊提供的功能。
/** 定義模塊 math.js **/ var basicNum = 0; var add = function (a, b) { return a + b; }; export { basicNum, add }; /** 引用模塊 **/ import { basicNum, add } from "./math"; function test(ele) { ele.textContent = add(99 + basicNum); }
如上例所示,使用import命令的時(shí)候,用戶(hù)需要知道所要加載的變量名或函數(shù)名,否則無(wú)法加載。為了給用戶(hù)提供方便,讓他們不用閱讀文檔就能加載模塊,就要用到export default命令,為模塊指定默認(rèn)輸出。
// export-default.js export default function () { console.log("foo"); }
// import-default.js import customName from "./export-default"; customName(); // "foo"
模塊默認(rèn)輸出, 其他模塊加載該模塊時(shí),import命令可以為該匿名函數(shù)指定任意名字。
(2)ES6 模塊與 CommonJS 模塊的差異它們有兩個(gè)重大差異:
① CommonJS 模塊輸出的是一個(gè)值的拷貝,ES6 模塊輸出的是值的引用。
② CommonJS 模塊是運(yùn)行時(shí)加載,ES6 模塊是編譯時(shí)輸出接口。
第二個(gè)差異是因?yàn)?CommonJS 加載的是一個(gè)對(duì)象(即module.exports屬性),該對(duì)象只有在腳本運(yùn)行完才會(huì)生成。而 ES6 模塊不是對(duì)象,它的對(duì)外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會(huì)生成。
下面重點(diǎn)解釋第一個(gè)差異,我們還是舉上面那個(gè)CommonJS模塊的加載機(jī)制例子:
// lib.js export let counter = 3; export function incCounter() { counter++; } // main.js import { counter, incCounter } from "./lib"; console.log(counter); // 3 incCounter(); console.log(counter); // 4
ES6 模塊的運(yùn)行機(jī)制與 CommonJS 不一樣。ES6 模塊是動(dòng)態(tài)引用,并且不會(huì)緩存值,模塊里面的變量綁定其所在的模塊。
(3) ES6-Babel-Browserify使用教程簡(jiǎn)單來(lái)說(shuō)就一句話(huà):使用Babel將ES6編譯為ES5代碼,使用Browserify編譯打包js。
①定義package.json文件{ "name" : "es6-babel-browserify", "version" : "1.0.0" }②安裝babel-cli, babel-preset-es2015和browserify
npm install babel-cli browserify -g
npm install babel-preset-es2015 --save-dev
preset 預(yù)設(shè)(將es6轉(zhuǎn)換成es5的所有插件打包)
③定義.babelrc文件{ "presets": ["es2015"] }④定義模塊代碼
//module1.js文件 // 分別暴露 export function foo() { console.log("foo() module1") } export function bar() { console.log("bar() module1") }
//module2.js文件 // 統(tǒng)一暴露 function fun1() { console.log("fun1() module2") } function fun2() { console.log("fun2() module2") } export { fun1, fun2 }
//module3.js文件 // 默認(rèn)暴露 可以暴露任意數(shù)據(jù)類(lèi)項(xiàng),暴露什么數(shù)據(jù),接收到就是什么數(shù)據(jù) export default () => { console.log("默認(rèn)暴露") }
// app.js文件 import { foo, bar } from "./module1" import { fun1, fun2 } from "./module2" import module3 from "./module3" foo() bar() fun1() fun2() module3()⑤ 編譯并在index.html中引入
使用Babel將ES6編譯為ES5代碼(但包含CommonJS語(yǔ)法) : babel js/src -d js/lib
使用Browserify編譯js : browserify js/lib/app.js -o js/lib/bundle.js
然后在index.html文件中引入
最后得到如下結(jié)果:
此外第三方庫(kù)(以jQuery為例)如何引入呢?
首先安裝依賴(lài)npm install jquery@1
然后在app.js文件中引入
//app.js文件 import { foo, bar } from "./module1" import { fun1, fun2 } from "./module2" import module3 from "./module3" import $ from "jquery" foo() bar() fun1() fun2() module3() $("body").css("background", "green")三、總結(jié)
CommonJS規(guī)范主要用于服務(wù)端編程,加載模塊是同步的,這并不適合在瀏覽器環(huán)境,因?yàn)橥揭馕吨枞虞d,瀏覽器資源是異步加載的,因此有了AMD CMD解決方案。
AMD規(guī)范在瀏覽器環(huán)境中異步加載模塊,而且可以并行加載多個(gè)模塊。不過(guò),AMD規(guī)范開(kāi)發(fā)成本高,代碼的閱讀和書(shū)寫(xiě)比較困難,模塊定義方式的語(yǔ)義不順暢。
CMD規(guī)范與AMD規(guī)范很相似,都用于瀏覽器編程,依賴(lài)就近,延遲執(zhí)行,可以很容易在Node.js中運(yùn)行。不過(guò),依賴(lài)SPM 打包,模塊的加載邏輯偏重
ES6 在語(yǔ)言標(biāo)準(zhǔn)的層面上,實(shí)現(xiàn)了模塊功能,而且實(shí)現(xiàn)得相當(dāng)簡(jiǎn)單,完全可以取代 CommonJS 和 AMD 規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案。
后記花了很長(zhǎng)時(shí)間(>10h)終于把"JS模塊化"講清楚,自己對(duì)模塊化的認(rèn)識(shí)又加深了一步,事實(shí)上,理解一件事并不難,難的是如何將一件事通俗分享給別人,并讓別人也有所收獲,一直以來(lái)我也是這樣要求自己!文章如有錯(cuò)誤和不正之處,歡迎指正和批評(píng),同時(shí)也希望大家多多支持,我會(huì)有更大的創(chuàng)作動(dòng)力!
參考文章 前端模塊化開(kāi)發(fā)那點(diǎn)歷史 CommonJS,AMD,CMD區(qū)別 AMD 和 CMD 的區(qū)別有哪些? Javascript模塊化編程 Javascript標(biāo)準(zhǔn)參考教程 CMD 模塊定義規(guī)范 理解CommonJS、AMD、CMD三種規(guī)范文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/100265.html
摘要:二模塊化規(guī)范概述應(yīng)用由模塊組成,采用模塊規(guī)范。模塊化語(yǔ)法命令用于規(guī)定模塊的對(duì)外接口,命令用于輸入其他模塊提供的功能。 前言 在JavaScript發(fā)展初期就是為了實(shí)現(xiàn)簡(jiǎn)單的頁(yè)面交互邏輯,寥寥數(shù)語(yǔ)即可;如今CPU、瀏覽器性能得到了極大的提升,很多頁(yè)面邏輯遷移到了客戶(hù)端(表單驗(yàn)證等),隨著web2.0時(shí)代的到來(lái),Ajax技術(shù)得到廣泛應(yīng)用,jQuery等前端庫(kù)層出不窮,前端代碼日益膨脹,此時(shí)...
摘要:參考資料前端模塊化詳解完整版入門(mén)近一萬(wàn)字的語(yǔ)法知識(shí)點(diǎn)補(bǔ)充徹底搞清楚中的和和詳解 前言 前端的模塊化之路經(jīng)歷了漫長(zhǎng)的過(guò)程,想詳細(xì)了解的小伙伴可以看浪里行舟大神寫(xiě)的前端模塊化詳解(完整版),這里根據(jù)幾位大佬們寫(xiě)的文章,將模塊化規(guī)范部分做了匯總和整理,希望讀完的小伙伴能有些收獲,也希望覺(jué)得有用的小伙伴可以點(diǎn)個(gè)贊,筆芯。 什么是模塊 將一個(gè)復(fù)雜的程序依據(jù)一定的規(guī)則(規(guī)范)封裝成幾個(gè)塊(文件)...
摘要:本文總結(jié)了組件間通信的幾種方式,如和,以通俗易懂的實(shí)例講述這其中的差別及使用場(chǎng)景,希望對(duì)小伙伴有些許幫助。狀態(tài)改變提交操作方法。 前言 組件是 vue.js最強(qiáng)大的功能之一,而組件實(shí)例的作用域是相互獨(dú)立的,這就意味著不同組件之間的數(shù)據(jù)無(wú)法相互引用。一般來(lái)說(shuō),組件可以有以下幾種關(guān)系:showImg(https://segmentfault.com/img/remote/146000001...
摘要:整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí)原文協(xié)作規(guī)范中文技術(shù)文檔協(xié)作規(guī)范阮一峰編程風(fēng)格凹凸實(shí)驗(yàn)室前端代碼規(guī)范風(fēng)格指南這一次,徹底弄懂執(zhí)行機(jī)制一次弄懂徹底解決此類(lèi)面試問(wèn)題瀏覽器與的事件循環(huán)有何區(qū)別筆試題事件循環(huán)機(jī)制異步編程理解的異步 better-learning 整理收藏一些優(yōu)秀的文章及大佬博客留著慢慢學(xué)習(xí) 原文:https://www.ahwgs.cn/youxiuwenzhan...
閱讀 3795·2021-10-11 10:59
閱讀 1396·2019-08-30 15:44
閱讀 3605·2019-08-29 16:39
閱讀 2982·2019-08-29 16:29
閱讀 1894·2019-08-29 15:24
閱讀 904·2019-08-29 15:05
閱讀 1333·2019-08-29 12:34
閱讀 2501·2019-08-29 12:19