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