主要解決
避免全局污染,解決命名沖突
JS設(shè)計(jì)初衷
JS誕生的時(shí)候,僅僅是為了實(shí)現(xiàn)網(wǎng)頁表單的本地校驗(yàn)和簡(jiǎn)單的DOM操作,并沒有模塊化的規(guī)范設(shè)計(jì)
隨著前端越來越豐富,一些問題隨之而來,于是模塊化才漸漸的趨于規(guī)范化
原始JS命名沖突
張三寫了一個(gè)腳本tab.js,李四寫了一個(gè)腳本index.js,如果命名有沖突,就會(huì)相互覆蓋
// 張三
var name = "產(chǎn)品";
// 李四
var name = "首頁";
前綴是之前變量沖突很好的解決方案,但是缺點(diǎn)一是命名不規(guī)范,缺點(diǎn)二是有些人的名稱簡(jiǎn)寫相同
// 張三
var zs_name = "產(chǎn)品";
// 李四
var ls_name = "首頁";
這時(shí)候,模塊化的思想就漸漸開始形成,比如模塊化演變一:命名空間
var tab = {
name:"產(chǎn)品"
}
var index = {
name:"首頁"
}
模塊化演變二:局部作用域
function tab () {
var name = "產(chǎn)品";
}
function index () {
var name = "首頁";
}
模塊化演變?nèi)鹤詧?zhí)行函數(shù)(已很好的隔離作用域,很多非標(biāo)準(zhǔn)模塊化的插件,腳本都是以這種形式書寫)
;(function () {
var name = "產(chǎn)品";
})();
;(function () {
var name = "首頁";
})();
最終演變
模塊化演變到現(xiàn)在出現(xiàn)了AMD,CMD,CommonJS(node方案)等模塊化標(biāo)準(zhǔn),然后前端模塊化進(jìn)入大爆發(fā)時(shí)代,未來的趨勢(shì)肯定是ES6的標(biāo)準(zhǔn)方案會(huì)逐漸統(tǒng)一,但是AMD,CMD、CommonJS和ES6的的標(biāo)準(zhǔn)方案相差不大
AMD標(biāo)準(zhǔn)
中文API地址https://github.com/amdjs/amdjs-api/wiki/AMD-(%E4%B8%AD%E6%96%87%E7%89%88)
模塊化逐步演變的過程中,AMD規(guī)范一直站前端主導(dǎo)地位,出現(xiàn)了很多實(shí)現(xiàn)AMD規(guī)范的插件,現(xiàn)在以requireJS(https://requirejs.org/)為例
requireJS出了個(gè)rJS專門針對(duì)nodeJS的模塊化標(biāo)準(zhǔn),但是一般不會(huì)使用
hello
在html頁面中引入
<!-- src是為了引入腳本,data-main設(shè)置入口文件 -->
<script src="./lib/require2.3.6.min.js" data-main="lib/main"></script>
lib/main.js
require(["moduleA", "moduleB"], function (moduleA, moduleB) {
console.log(moduleA);
console.log(moduleB);
})
lib/moduleA.js
define(() => ({
name: "產(chǎn)品"
}));
lib/moduleB.js
define(() => ({
name: "首頁"
}));
源碼解析(非完全)
執(zhí)行require函數(shù)時(shí),得到模塊的相對(duì)路徑,生成script腳本,比如:<script async src="加載的模塊地址"></script>,并添加到head標(biāo)簽中,添加偵聽腳本加載完成事件
// 源碼截取
req.createNode = function (config, moduleName, url) {
var node = config.xhtml ?
document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
document.createElement('script');
node.type = config.scriptType || 'text/javascript';
node.charset = 'utf-8';
node.async = true;
return node;
};
運(yùn)行動(dòng)態(tài)添加的腳本語言,執(zhí)行模塊的define函數(shù),參數(shù)push到全局隊(duì)列
// 源碼截取
define = function (name, deps, callback) {
// 很多省略代碼
if (context) {
context.defQueue.push([name, deps, callback]);
context.defQueueMap[name] = true;
} else {
globalDefQueue.push([name, deps, callback]);
}
};
執(zhí)行require的回調(diào)函數(shù),取出隊(duì)列作為參數(shù)
define函數(shù)的參數(shù)
語法:
name:模塊名稱,如果沒有命名則為引入腳本的名字。如果寫了name,模塊名必須是頂級(jí)的和絕對(duì)的(不允許用相對(duì)名字)
dependencies:依賴項(xiàng)
callback:回調(diào)函數(shù),返回模塊內(nèi)容
define有amd屬性, 它的值是一個(gè)對(duì)象,這是為了規(guī)范編程規(guī)則,可以防止與現(xiàn)有定義了define函數(shù)但不遵從AMD編程接口的代碼相沖突
define.amd = {
jQuery: true
};
怎么判斷一個(gè)庫支不支持Amd標(biāo)準(zhǔn),判斷define是全局函數(shù),且define有amd屬性,比如jquery
if (typeof define === "function" && define.amd) {
define("jquery", [], function () {
return jQuery;
});
}
define可以引入依賴模塊,依賴模塊的地址可以用require.config進(jìn)行路徑映射(見下)
define(['jquery'], function ($) {
'use strict';
$.get("/user/info", (res) => {
console.log(res);
})
});
define的標(biāo)準(zhǔn)寫法是使用exports一個(gè)對(duì)象作為返回對(duì)象,如果沒有exports,會(huì)以函數(shù)的返回值作為返回對(duì)象
define("moduleA", ["exports"], (exports) => {
exports.moduleA = { name: "產(chǎn)品" }
})
使用exports導(dǎo)出的對(duì)象的格式變?yōu)閧 moduleA: {name: "產(chǎn)品"} },而不是{name: "產(chǎn)品"},如果想實(shí)現(xiàn)return效果可以使用解構(gòu)
require(["moduleA", "moduleB"], function ({ moduleA }, moduleB) {
console.log(moduleA);
console.log(moduleB);
})
defined還有一種寫法,引入require模塊后,使用require引入其他模塊。依賴模塊非常多時(shí),這種寫法比較簡(jiǎn)潔
define("moduleA", ["require", "jquery"], (require) => {
let $ = require("jquery");
console.log($.fn);
return { name: "產(chǎn)品" };
})
require函數(shù)
require可以有一個(gè)config屬性,可以配置模塊路徑
require.config({
paths: {
jquery: "../lib/jquery.js"
}
});
CMD標(biāo)準(zhǔn)
sea.js(國(guó)產(chǎn))在推廣過程中,逐漸形成了CMD規(guī)范,跟AMD比較類似,并且兼容CommonJS的模塊寫法,但是目前已不再維護(hù)
CMD推崇的是就近依賴,AMD則默認(rèn)約束模塊一開始就聲明相關(guān)依賴,其他定義方式及模塊相關(guān)變量都很相似
CMD寫法
define((require, exports, module) => {
// 模塊代碼
})
CommonJS標(biāo)準(zhǔn)
這是nodeJS的模塊化標(biāo)準(zhǔn),致力于前后端統(tǒng)一的模塊化標(biāo)準(zhǔn),差點(diǎn)統(tǒng)一模塊化天下
模塊寫法
module.exports = {
name: "首頁"
}
在CommonJS規(guī)范中,module.exports和exports相等,所以module.exports可以簡(jiǎn)寫為exports
exports = {
name: "首頁"
}
模塊引入,不加后綴表示js文件(注意,CMD和AMD如果和入口文件在同一目錄下,可以省略./,但是CommonJS不行)
let moduleA = require("./moduleA");
console.log(moduleA);
模塊引入可解構(gòu)
let { name } = require("./moduleA");
console.log(name);
可引入內(nèi)置文件(Node自帶功能)
const path = require("path");
引入node_modules文件夾的內(nèi)容,也可以直接寫名稱,比如:cnpm i jquery,則引入時(shí),可以用以下方式
const jquery = require("jquery");
ES6模塊化標(biāo)準(zhǔn)
未來模塊化的一統(tǒng)標(biāo)準(zhǔn)
定義模塊
模塊寫法一
export var a = 10;
export var f = function () {
console.log(1);
}
模塊寫法二
var a = 10;
var f = function () {
console.log(1);
}
export { a, f }
模塊寫法三(export default一個(gè)頁面只有有一個(gè),export可以有多個(gè),可共存)
export default {
name: "首頁"
}
模塊寫法可以使用別名
function v1() { }
function v2() { }
export {
v1 as streamV1,
v2 as streamV2
}
引入模塊
假如有如下定義模塊
let name = "首頁";
let version = "2.0.7";
export {
name, version
}
export default {
name, version
}
引入處理
使用export導(dǎo)出的模塊,必須用import并用{ }來接收
export default導(dǎo)出,比如用一個(gè)對(duì)象接收,且不支持解構(gòu)
import { name, version } from "./moduleA"
import info from "./moduleA"
默認(rèn)export導(dǎo)出的內(nèi)容無法用對(duì)象接收,export default導(dǎo)出的內(nèi)容無法解構(gòu),如果希望export導(dǎo)出的內(nèi)容也能用對(duì)象接收,可以用關(guān)鍵字as申明別名
// 聲明
let name = "首頁";
let version = "2.0.7";
export {
name, version
}
// 接收
import * as info from "./moduleA"