前端模塊化

模塊化

模塊化用來分割,組織和打包軟件。每個模塊完成一個特定的子功能,所有的模塊按某種方法組裝起來,成爲一個整體,完成整個系統所要求的功能。

以前開發沒用模塊化可能導致的問題

  1. 命名空間衝突,多個庫可能會使用同一名稱
  2. 無法合理的管理項目的依賴和版本
  3. 無法方便的控制依賴的加載順序

## 如今前端模塊化的主流方式
利用自動化構建工具Gulp/Webpack 把源代碼轉換成發佈線上的可執行JavaScrip、CSS、HTML 代碼
通常自動化構建工具做的內容有:

  • 代碼轉換:ECMASCRIPT6 編譯成 ECMASCRIPT5、LESS 編譯成 CSS 等。
  • 文件優化:壓縮 JavaScript、CSS、HTML 代碼,壓縮合並圖片等
  • 代碼分割:提取多個頁面的公共代碼、提取首屏不需要執行部分的代碼讓其異步加載。
  • 模塊合併:在採用模塊化的項目裏會有很多個模塊和文件,需要構建功能把模塊分類合併成一個文件。
  • 自動刷新:監聽本地源代碼的變化,自動重新構建、刷新瀏覽器。
  • 代碼校驗:在代碼被提交到倉庫前需要校驗代碼是否符合規範,以及單元測試是否通過。
  • 自動發佈:更新完代碼後,自動構建出線上發佈代碼並傳輸給發佈系統。

## 模塊化的幾種規範

  1. CommonJS
  2. AMD
  3. 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

優點

  1. 可在不轉換代碼的情況下直接在瀏覽器中運行
  2. 可加載多個依賴
  3. 代碼可運行在瀏覽器環境和 Node.js 環境下

缺點

  1. 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`規範,成爲瀏覽器和服務器通用的模塊解決方案。

  • 優點

    1. 主流方案, 各種瀏覽器廠商和node都逐漸支持
  • 缺點

    1. 目前無法直接運行在大部分 JavaScript 運行環境下,必須通過工具轉換成標準的 ES5 後才能正常運行
  • 特點

    1. 如果你通過本地加載Html 文件 (比如一個 file:// 路徑的文件), 你將會遇到 CORS 錯誤,因爲Javascript 模塊安全性需要。你需要通過一個服務器來測試。
    2. 自動使用嚴格模式。
    3. 加載一個模塊腳本時不需要使用 defer 屬性, 模塊會自動延遲加載。
    4. 無法在全局獲得. 因此,你只能在導入這些功能的腳本文件中使用他們,你也無法通過Javascript console 中獲取到他們
  • 使用方法
  1. 基本使用
// 導出
export const name = 'xiaoming';
// 導入
import { name } from './person.js';
  1. 默認導出
// 導出
const name = 'xiaoming';
export default name
// 導入
import name from './person.js';
//  或者
import {default as name} from './person.js';
  1. 重命名導出與導入
export {
  function1 as newFunctionName,
  function2 as anotherNewFunctionName
};

// inside main.mjs
import { newFunctionName, anotherNewFunctionName } from '/modules';
  1. 全部導入
import * as Module from '/modules/module';
Module.function1()
Module.function2()
  1. 動態導入
import('/modules/myModule.mjs')
  .then((module) => {
    // Do something with the module.
  });
//    需要babel解析才能使用

參考資料

珠峯培訓
深入理解commonjs
AMD與requireJS
JavaScript modules 模塊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章