摘要:感謝感謝和在推動模塊化發(fā)展方面做出的貢獻。與引用阮一峰老師的標(biāo)準(zhǔn)參考教程規(guī)范加載模塊是同步的,也就是說,只有加載完成,才能執(zhí)行后面的操作。規(guī)定了新的模塊加載方案。與引用阮一峰老師的入門它們有兩個重大差異。
前言
本篇我們重點介紹以下四種模塊加載規(guī)范:
AMD
CMD
CommonJS
ES6 模塊
最后再延伸講下 Babel 的編譯和 webpack 的打包原理。
require.js在了解 AMD 規(guī)范之前,我們先來看看 require.js 的使用方式。
項目目錄為:
* project/ * index.html * vender/ * main.js * require.js * add.js * square.js * multiply.js
index.html 的內(nèi)容如下:
require.js Content
data-main="vender/main" 表示主模塊是 vender 下的 main.js。
main.js 的配置如下:
// main.js require(["./add", "./square"], function(addModule, squareModule) { console.log(addModule.add(1, 1)) console.log(squareModule.square(3)) });
require 的第一個參數(shù)表示依賴的模塊的路徑,第二個參數(shù)表示此模塊的內(nèi)容。
由此可以看出,主模塊依賴 add 模塊和 square 模塊。
我們看下 add 模塊即 add.js 的內(nèi)容:
// add.js define(function() { console.log("加載了 add 模塊"); var add = function(x, y) { return x + y; }; return { add: add }; });
requirejs 為全局添加了 define 函數(shù),你只要按照這種約定的方式書寫這個模塊即可。
那如果依賴的模塊又依賴了其他模塊呢?
我們來看看主模塊依賴的 square 模塊, square 模塊的作用是求出一個數(shù)字的平方,比如輸入 3 就返回 9,該模塊依賴一個乘法模塊,該乘法模塊即 multiply.js 的代碼如下:
// multiply.js define(function() { console.log("加載了 multiply 模塊") var multiply = function(x, y) { return x * y; }; return { multiply: multiply }; });
而 square 模塊就要用到 multiply 模塊,其實寫法跟 main.js 添加依賴模塊一樣:
// square.js define(["./multiply"], function(multiplyModule) { console.log("加載了 square 模塊") return { square: function(num) { return multiplyModule.multiply(num, num) } }; });
require.js 會自動分析依賴關(guān)系,將需要加載的模塊正確加載。
requirejs 項目 Demo 地址:https://github.com/mqyqingfeng/Blog/tree/master/demos/ES6/module/requirejs
而如果我們在瀏覽器中打開 index.html,打印的順序為:
加載了 add 模塊 加載了 multiply 模塊 加載了 square 模塊 2 9AMD
在上節(jié),我們說了這樣一句話:
requirejs 為全局添加了 define 函數(shù),你只要按照這種約定的方式書寫這個模塊即可。
那這個約定的書寫方式是指什么呢?
指的便是 The Asynchronous Module Definition (AMD) 規(guī)范。
所以其實 AMD 是 RequireJS 在推廣過程中對模塊定義的規(guī)范化產(chǎn)出。
你去看 AMD 規(guī)范) 的內(nèi)容,其主要內(nèi)容就是定義了 define 函數(shù)該如何書寫,只要你按照這個規(guī)范書寫模塊和依賴,require.js 就能正確的進行解析。
sea.js在國內(nèi),經(jīng)常與 AMD 被一起提起的還有 CMD,CMD 又是什么呢?我們從 sea.js 的使用開始說起。
文件目錄與 requirejs 項目目錄相同:
* project/ * index.html * vender/ * main.js * require.js * add.js * square.js * multiply.js
index.html 的內(nèi)容如下:
sea.js Content
main.js 的內(nèi)容如下:
// main.js define(function(require, exports, module) { var addModule = require("./add"); console.log(addModule.add(1, 1)) var squareModule = require("./square"); console.log(squareModule.square(3)) });
add.js 的內(nèi)容如下:
// add.js define(function(require, exports, module) { console.log("加載了 add 模塊") var add = function(x, y) { return x + y; }; module.exports = { add: add }; });
square.js 的內(nèi)容如下:
define(function(require, exports, module) { console.log("加載了 square 模塊") var multiplyModule = require("./multiply"); module.exports = { square: function(num) { return multiplyModule.multiply(num, num) } }; });
multiply.js 的內(nèi)容如下:
define(function(require, exports, module) { console.log("加載了 multiply 模塊") var multiply = function(x, y) { return x * y; }; module.exports = { multiply: multiply }; });
跟第一個例子是同樣的依賴結(jié)構(gòu),即 main 依賴 add 和 square,square 又依賴 multiply。
seajs 項目 Demo 地址:https://github.com/mqyqingfeng/Blog/tree/master/demos/ES6/module/seajs
而如果我們在瀏覽器中打開 index.html,打印的順序為:
加載了 add 模塊 2 加載了 square 模塊 加載了 multiply 模塊 9CMD
與 AMD 一樣,CMD 其實就是 SeaJS 在推廣過程中對模塊定義的規(guī)范化產(chǎn)出。
你去看 CMD 規(guī)范的內(nèi)容,主要內(nèi)容就是描述該如何定義模塊,如何引入模塊,如何導(dǎo)出模塊,只要你按照這個規(guī)范書寫代碼,sea.js 就能正確的進行解析。
AMD 與 CMD 的區(qū)別從 sea.js 和 require.js 的例子可以看出:
1.CMD 推崇依賴就近,AMD 推崇依賴前置。看兩個項目中的 main.js:
// require.js 例子中的 main.js // 依賴必須一開始就寫好 require(["./add", "./square"], function(addModule, squareModule) { console.log(addModule.add(1, 1)) console.log(squareModule.square(3)) });
// sea.js 例子中的 main.js define(function(require, exports, module) { var addModule = require("./add"); console.log(addModule.add(1, 1)) // 依賴可以就近書寫 var squareModule = require("./square"); console.log(squareModule.square(3)) });
2.對于依賴的模塊,AMD 是提前執(zhí)行,CMD 是延遲執(zhí)行。看兩個項目中的打印順序:
// require.js 加載了 add 模塊 加載了 multiply 模塊 加載了 square 模塊 2 9
// sea.js 加載了 add 模塊 2 加載了 square 模塊 加載了 multiply 模塊 9
AMD 是將需要使用的模塊先加載完再執(zhí)行代碼,而 CMD 是在 require 的時候才去加載模塊文件,加載完再接著執(zhí)行。
感謝感謝 require.js 和 sea.js 在推動 JavaScript 模塊化發(fā)展方面做出的貢獻。
CommonJSAMD 和 CMD 都是用于瀏覽器端的模塊規(guī)范,而在服務(wù)器端比如 node,采用的則是 CommonJS 規(guī)范。
導(dǎo)出模塊的方式:
var add = function(x, y) { return x + y; }; module.exports.add = add;
引入模塊的方式:
var add = require("./add.js"); console.log(add.add(1, 1));
我們將之前的例子改成 CommonJS 規(guī)范:
// main.js var add = require("./add.js"); console.log(add.add(1, 1)) var square = require("./square.js"); console.log(square.square(3));
// add.js console.log("加載了 add 模塊") var add = function(x, y) { return x + y; }; module.exports.add = add;
// multiply.js console.log("加載了 multiply 模塊") var multiply = function(x, y) { return x * y; }; module.exports.multiply = multiply;
// square.js console.log("加載了 square 模塊") var multiply = require("./multiply.js"); var square = function(num) { return multiply.multiply(num, num); }; module.exports.square = square;
CommonJS 項目 Demo 地址:https://github.com/mqyqingfeng/Blog/tree/master/demos/ES6/module/commonJS
如果我們執(zhí)行 node main.js,打印的順序為:
加載了 add 模塊 2 加載了 square 模塊 加載了 multiply 模塊 9
跟 sea.js 的執(zhí)行結(jié)果一致,也是在 require 的時候才去加載模塊文件,加載完再接著執(zhí)行。
CommonJS 與 AMD引用阮一峰老師的《JavaScript 標(biāo)準(zhǔn)參考教程(alpha)》:
CommonJS 規(guī)范加載模塊是同步的,也就是說,只有加載完成,才能執(zhí)行后面的操作。ES6AMD規(guī)范則是非同步加載模塊,允許指定回調(diào)函數(shù)。
由于 Node.js 主要用于服務(wù)器編程,模塊文件一般都已經(jīng)存在于本地硬盤,所以加載起來比較快,不用考慮非同步加載的方式,所以 CommonJS 規(guī)范比較適用。
但是,如果是瀏覽器環(huán)境,要從服務(wù)器端加載模塊,這時就必須采用非同步模式,因此瀏覽器端一般采用 AMD 規(guī)范。
ECMAScript2015 規(guī)定了新的模塊加載方案。
導(dǎo)出模塊的方式:
var firstName = "Michael"; var lastName = "Jackson"; var year = 1958; export {firstName, lastName, year};
引入模塊的方式:
import {firstName, lastName, year} from "./profile";
我們再將上面的例子改成 ES6 規(guī)范:
目錄結(jié)構(gòu)與 requirejs 和 seajs 目錄結(jié)構(gòu)一致。
ES6 Content
注意!瀏覽器加載 ES6 模塊,也使用 標(biāo)簽,但是要加入 type="module" 屬性。
// main.js import {add} from "./add.js"; console.log(add(1, 1)) import {square} from "./square.js"; console.log(square(3));
// add.js console.log("加載了 add 模塊") var add = function(x, y) { return x + y; }; export {add}
// multiply.js console.log("加載了 multiply 模塊") var multiply = function(x, y) { return x * y; }; export {multiply}
// square.js console.log("加載了 square 模塊") import {multiply} from "./multiply.js"; var square = function(num) { return multiply(num, num); }; export {square}
ES6-Module 項目 Demo 地址:https://github.com/mqyqingfeng/Blog/tree/master/demos/ES6/module/ES6
值得注意的,在 Chrome 中,如果直接打開,會報跨域錯誤,必須開啟服務(wù)器,保證文件同源才可以有效果。
為了驗證這個效果你可以:
cnpm install http-server -g
然后進入該目錄,執(zhí)行
http-server
在瀏覽器打開 http://localhost:8080/ 即可查看效果。
打印的順序為:
加載了 add 模塊 加載了 multiply 模塊 加載了 square 模塊 2 9
跟 require.js 的執(zhí)行結(jié)果是一致的,也就是將需要使用的模塊先加載完再執(zhí)行代碼。
ES6 與 CommonJS引用阮一峰老師的 《ECMAScript 6 入門》:
它們有兩個重大差異。
CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。
第二個差異可以從兩個項目的打印結(jié)果看出,導(dǎo)致這種差別的原因是:
因為 CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會生成。
重點解釋第一個差異。
CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內(nèi)部的變化就影響不到這個值。
舉個例子:
// 輸出模塊 counter.js var counter = 3; function incCounter() { counter++; } module.exports = { counter: counter, incCounter: incCounter, };
// 引入模塊 main.js var mod = require("./counter"); console.log(mod.counter); // 3 mod.incCounter(); console.log(mod.counter); // 3
counter.js 模塊加載以后,它的內(nèi)部變化就影響不到輸出的 mod.counter 了。這是因為 mod.counter 是一個原始類型的值,會被緩存。
但是如果修改 counter 為一個引用類型的話:
// 輸出模塊 counter.js var counter = { value: 3 }; function incCounter() { counter.value++; } module.exports = { counter: counter, incCounter: incCounter, };
// 引入模塊 main.js var mod = require("./counter.js"); console.log(mod.counter.value); // 3 mod.incCounter(); console.log(mod.counter.value); // 4
value 是會發(fā)生改變的。不過也可以說這是 "值的拷貝",只是對于引用類型而言,值指的其實是引用。
而如果我們將這個例子改成 ES6:
// counter.js export let counter = 3; export function incCounter() { counter++; } // main.js import { counter, incCounter } from "./counter"; console.log(counter); // 3 incCounter(); console.log(counter); // 4
這是因為
ES6 模塊的運行機制與 CommonJS 不一樣。JS 引擎對腳本靜態(tài)分析的時候,遇到模塊加載命令 import,就會生成一個只讀引用。等到腳本真正執(zhí)行時,再根據(jù)這個只讀引用,到被加載的那個模塊里面去取值。換句話說,ES6 的 import 有點像 Unix 系統(tǒng)的“符號連接”,原始值變了,import 加載的值也會跟著變。因此,ES6 模塊是動態(tài)引用,并且不會緩存值,模塊里面的變量綁定其所在的模塊。Babel
鑒于瀏覽器支持度的問題,如果要使用 ES6 的語法,一般都會借助 Babel,可對于 import 和 export 而言,只借助 Babel 就可以嗎?
讓我們看看 Babel 是怎么編譯 import 和 export 語法的。
// ES6 var firstName = "Michael"; var lastName = "Jackson"; var year = 1958; export {firstName, lastName, year};
// Babel 編譯后 "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var firstName = "Michael"; var lastName = "Jackson"; var year = 1958; exports.firstName = firstName; exports.lastName = lastName; exports.year = year;
是不是感覺有那么一點奇怪?編譯后的語法更像是 CommonJS 規(guī)范,再看 import 的編譯結(jié)果:
// ES6 import {firstName, lastName, year} from "./profile";
// Babel 編譯后 "use strict"; var _profile = require("./profile");
你會發(fā)現(xiàn) Babel 只是把 ES6 模塊語法轉(zhuǎn)為 CommonJS 模塊語法,然而瀏覽器是不支持這種模塊語法的,所以直接跑在瀏覽器會報錯的,如果想要在瀏覽器中運行,還是需要使用打包工具將代碼打包。
webpackBabel 將 ES6 模塊轉(zhuǎn)為 CommonJS 后, webpack 又是怎么做的打包的呢?它該如何將這些文件打包在一起,從而能保證正確的處理依賴,以及能在瀏覽器中運行呢?
首先為什么瀏覽器中不支持 CommonJS 語法呢?
這是因為瀏覽器環(huán)境中并沒有 module、 exports、 require 等環(huán)境變量。
換句話說,webpack 打包后的文件之所以在瀏覽器中能運行,就是靠模擬了這些變量的行為。
那怎么模擬呢?
我們以 CommonJS 項目中的 square.js 為例,它依賴了 multiply 模塊:
console.log("加載了 square 模塊") var multiply = require("./multiply.js"); var square = function(num) { return multiply.multiply(num, num); }; module.exports.square = square;
webpack 會將其包裹一層,注入這些變量:
function(module, exports, require) { console.log("加載了 square 模塊"); var multiply = require("./multiply"); module.exports = { square: function(num) { return multiply.multiply(num, num); } }; }
那 webpack 又會將 CommonJS 項目的代碼打包成什么樣呢?我寫了一個精簡的例子,你可以直接復(fù)制到瀏覽器中查看效果:
// 自執(zhí)行函數(shù) (function(modules) { // 用于儲存已經(jīng)加載過的模塊 var installedModules = {}; function require(moduleName) { if (installedModules[moduleName]) { return installedModules[moduleName].exports; } var module = installedModules[moduleName] = { exports: {} }; modules[moduleName](module, module.exports, require); return module.exports; } // 加載主模塊 return require("main"); })({ "main": function(module, exports, require) { var addModule = require("./add"); console.log(addModule.add(1, 1)) var squareModule = require("./square"); console.log(squareModule.square(3)); }, "./add": function(module, exports, require) { console.log("加載了 add 模塊"); module.exports = { add: function(x, y) { return x + y; } }; }, "./square": function(module, exports, require) { console.log("加載了 square 模塊"); var multiply = require("./multiply"); module.exports = { square: function(num) { return multiply.multiply(num, num); } }; }, "./multiply": function(module, exports, require) { console.log("加載了 multiply 模塊"); module.exports = { multiply: function(x, y) { return x * y; } }; } })
最終的執(zhí)行結(jié)果為:
加載了 add 模塊 2 加載了 square 模塊 加載了 multiply 模塊 9參考
《JavaScript 標(biāo)準(zhǔn)參考教程(alpha)》
《ECMAScript6 入門》
手寫一個CommonJS打包工具(一)
ES6 系列ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預(yù)計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標(biāo)簽?zāi)0濉⒓^函數(shù)、Symbol、Set、Map 以及 Promise 的模擬實現(xiàn)、模塊加載方案、異步處理等內(nèi)容。
如果有錯誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑垊?wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對作者也是一種鼓勵。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/99239.html
摘要:在上面的列表中,是自解釋型的。我們將使用后者。調(diào)整文件的內(nèi)容到這一步,這個應(yīng)用就具備熱刷新的功能。下一步,更新文件中的到現(xiàn)在為止,如果你在控制臺運行壓縮文件將被創(chuàng)建并且放在路徑下面。 這是React和ECMAScript2015系列文章的最后一篇,我們將繼續(xù)探索React 和 Webpack的使用。 下面是所有系列文章章節(jié)的鏈接: React 、 ES6 - 介紹(第一部分) Rea...
摘要:在上面的列表中,是自解釋型的。我們將使用后者。調(diào)整文件的內(nèi)容到這一步,這個應(yīng)用就具備熱刷新的功能。下一步,更新文件中的到現(xiàn)在為止,如果你在控制臺運行壓縮文件將被創(chuàng)建并且放在路徑下面。 這是React和ECMAScript2015系列文章的最后一篇,我們將繼續(xù)探索React 和 Webpack的使用。 下面是所有系列文章章節(jié)的鏈接: React 、 ES6 - 介紹(第一部分) Rea...
摘要:希望幫助更多的前端愛好者學(xué)習(xí)。前端開發(fā)者指南作者科迪林黎,由前端大師傾情贊助。翻譯最佳實踐譯者張捷滬江前端開發(fā)工程師當(dāng)你問起有關(guān)與時,老司機們首先就會告訴你其實是個沒有網(wǎng)絡(luò)請求功能的庫。 前端基礎(chǔ)面試題(JS部分) 前端基礎(chǔ)面試題(JS部分) 學(xué)習(xí) React.js 比你想象的要簡單 原文地址:Learning React.js is easier than you think 原文作...
摘要:系列種優(yōu)化頁面加載速度的方法隨筆分類中個最重要的技術(shù)點常用整理網(wǎng)頁性能管理詳解離線緩存簡介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問性能優(yōu)化方案實現(xiàn)的大排序算法一怪對象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁面加載速度的方法 隨筆分類 - HTML5 HTML5中40個最重要的技術(shù)點 常用meta整理 網(wǎng)頁性能管理詳解 HTML5 ...
閱讀 2039·2021-11-08 13:14
閱讀 2945·2021-10-18 13:34
閱讀 2034·2021-09-23 11:21
閱讀 3597·2019-08-30 15:54
閱讀 1764·2019-08-30 15:54
閱讀 2936·2019-08-29 15:33
閱讀 2589·2019-08-29 14:01
閱讀 1950·2019-08-29 13:52