模塊化

主要解決

避免全局污染,解決命名沖突

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函數的參數

語法:\color{red}{define(name?:string, dependencies?:string [], callback)}

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"
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。