模塊化
模塊化用來分割,組織和打包軟件。每個模塊完成一個特定的子功能,所有的模塊按某種方法組裝起來,成爲一個整體,完成整個系統所要求的功能。
以前開發沒用模塊化可能導致的問題
- 命名空間衝突,多個庫可能會使用同一名稱
- 無法合理的管理項目的依賴和版本
- 無法方便的控制依賴的加載順序
## 如今前端模塊化的主流方式
利用自動化構建工具Gulp/Webpack 把源代碼轉換成發佈線上的可執行JavaScrip、CSS、HTML 代碼
通常自動化構建工具做的內容有:
- 代碼轉換:ECMASCRIPT6 編譯成 ECMASCRIPT5、LESS 編譯成 CSS 等。
- 文件優化:壓縮 JavaScript、CSS、HTML 代碼,壓縮合並圖片等
- 代碼分割:提取多個頁面的公共代碼、提取首屏不需要執行部分的代碼讓其異步加載。
- 模塊合併:在採用模塊化的項目裏會有很多個模塊和文件,需要構建功能把模塊分類合併成一個文件。
- 自動刷新:監聽本地源代碼的變化,自動重新構建、刷新瀏覽器。
- 代碼校驗:在代碼被提交到倉庫前需要校驗代碼是否符合規範,以及單元測試是否通過。
- 自動發佈:更新完代碼後,自動構建出線上發佈代碼並傳輸給發佈系統。
## 模塊化的幾種規範
- CommonJS
- AMD
- ES6模塊化
## CommonJS
CommonJS定義的模塊分爲:{模塊引用(require)} {模塊定義(exports)} {模塊標識(module)}
require()用來引入外部模塊;exports對象用於導出當前模塊的方法或變量,唯一的導出口;module對象就代表模塊本身。
- 使用方式
// foo.js
module.exports = function(x) { // 導出
console.log(x);
};
// main.js
var foo = require("./foo"); // 導入
foo("Hi");
- 原理
瀏覽器不兼容CommonJS的根本原因,在於缺少四個Node.js環境的變量。
- module
- exports
- require
- global
換言之, 只要我們提供了這幾個, 瀏覽器就能加載 CommonJS 模塊。下面是一個簡單的示例
var module = {
exports: {}
};
(function(module, exports) {
exports.multiply = function (n) { return n * 1000 };
}(module, module.exports))
var f = module.exports.multiply;
f(5) // 5000
- 模擬實現require
function require(p){
var path = require.resolve(p); // 返回路徑
var mod = require.modules[path]; // 是否已註冊
if (!mod) throw new Error('failed to require "' + p + '"'); // 未註冊拋出
if (!mod.exports) { // 如果註冊了 就執行模塊
mod.exports = {};
mod.call(mod.exports, mod, mod.exports, require.relative(path)); // 執行模塊
}
return mod.exports;
}
require.modules = {};
require.resolve = function (path){
var orig = path;
var reg = path + '.js';
var index = path + '/index.js';
return require.modules[reg] && reg || require.modules[index] && index || orig;
};
require.register = function (path, fn){ // 註冊模塊
require.modules[path] = fn;
};
require.relative = function (parent) {
return function(p){
if ('.' != p.charAt(0)) return require(p);
var path = parent.split('/');
var segs = p.split('/');
path.pop();
for (var i = 0; i < segs.length; i++) {
var seg = segs[i];
if ('..' == seg) path.pop();
else if ('.' != seg) path.push(seg);
}
return require(path.join('/'));
};
};
require.register("moduleId", function(module, exports, require){
// 代碼寫在這裏
});
var result = require("moduleId");
AMD
優點
- 可在不轉換代碼的情況下直接在瀏覽器中運行
- 可加載多個依賴
- 代碼可運行在瀏覽器環境和 Node.js 環境下
缺點
- JavaScript 運行環境沒有原生支持 AMD,需要先導入實現了 AMD 的庫後才能正常使用
- 語法
define([module-name?], // 模塊名
[array-of-dependencies?], // 依賴模塊
[module-factory-or-object]); // 模塊的實現,或者一個JavaScript對象
- 使用方法
define('a', [], function () {
return 'a';
});
define('b', ['a'], function (a) {
return a + 'b';
});
// 導入和使用
require(['b'], function (b) {
console.log(b);
});
define函數具有異步性。當define函數執行時,首先會異步的去調用第二個參數中列出的依賴模塊,當所有的模塊被載入完成之後,
如果第三個參數是一個回調函數則執行;然後告訴系統模塊可用,也就通知了依賴於自己的模塊自己已經可用
- 模擬實現
let factories = {};
function define(modName, dependencies, factory) {
factory.dependencies = dependencies; // 依賴
factories[modName] = factory; // 儲存模塊
}
function require(modNames, callback) {
let loadedModNames = modNames.map(function (modName) {
let factory = factories[modName];
let dependencies = factory.dependencies;
let exports;
require(dependencies, function (...dependencyMods) {
exports = factory.apply(null, dependencyMods);
});
return exports;
})
callback.apply(null, loadedModNames);
}
目前AMD使用比較少了
ES6模塊化
ES6 模塊化是ECMA提出的JavaScript模塊化規範,它在語言的層面上實現了模塊化。瀏覽器廠商和Node.js
都宣佈要原生支持該規範。它將逐漸取代CommonJS和AMD`規範,成爲瀏覽器和服務器通用的模塊解決方案。
-
優點
- 主流方案, 各種瀏覽器廠商和node都逐漸支持
-
缺點
- 目前無法直接運行在大部分 JavaScript 運行環境下,必須通過工具轉換成標準的 ES5 後才能正常運行
-
特點
- 如果你通過本地加載Html 文件 (比如一個 file:// 路徑的文件), 你將會遇到 CORS 錯誤,因爲Javascript 模塊安全性需要。你需要通過一個服務器來測試。
- 自動使用嚴格模式。
- 加載一個模塊腳本時不需要使用 defer 屬性, 模塊會自動延遲加載。
- 無法在全局獲得. 因此,你只能在導入這些功能的腳本文件中使用他們,你也無法通過Javascript console 中獲取到他們
- 使用方法
- 基本使用
// 導出
export const name = 'xiaoming';
// 導入
import { name } from './person.js';
- 默認導出
// 導出
const name = 'xiaoming';
export default name
// 導入
import name from './person.js';
// 或者
import {default as name} from './person.js';
- 重命名導出與導入
export {
function1 as newFunctionName,
function2 as anotherNewFunctionName
};
// inside main.mjs
import { newFunctionName, anotherNewFunctionName } from '/modules';
- 全部導入
import * as Module from '/modules/module';
Module.function1()
Module.function2()
- 動態導入
import('/modules/myModule.mjs')
.then((module) => {
// Do something with the module.
});
// 需要babel解析才能使用