WEB 前端模塊化都有什麼?

前言

說到前端模塊化,你第一時間能想到的是什麼?Webpack?ES6 Module?還有嗎?咱們一塊兒來看一下下圖。 javascript

模塊化關鍵詞
相信大夥兒對上圖的單詞都不陌生,可能用過、看過或者是隻是聽過。那你能不能用一張圖梳理清楚上述全部詞彙之間的關係呢?咱們平常編寫代碼的時候,又和他們之間的誰誰誰有關係呢?

1、千絲萬縷

爲了更貼合咱們的平常開發場景(先後端分離),咱們嘗試先從不一樣平臺的維度區分,做爲本文的切入點。php

1. 根據平臺劃分

平臺 規範 特性
瀏覽器 AMD、CMD 存在網絡瓶頸,使用異步加載
非瀏覽器 CommonJS 直接操做 IO,同步加載

能夠看到咱們很是暴力的以是否是瀏覽器做爲劃分標準。仔細分析一下,他們之間最大的差別在於其特性上,是否存在瓶頸。 例如說網絡性能瓶頸,每一個模塊的請求都須要發起一次網絡請求,並等待資源下載完成後再進行下一步操做,那整個用戶體驗是很是糟糕的。 根據該場景,咱們簡化一下,以同步加載和異步加載兩個維度進行區分。前端

特性 規範
同步加載 CommonJS
異步加載 AMD、CMD

2. AMD、CMD 兩大規範

先忽略 CommonJS,咱們先介紹下,曾經一度盛行的 AMD、CMD 兩大規範。java

規範 約束條件 表明做
AMD 依賴前置 requirejs
CMD 就近依賴 seajs

AMD、CMD 提供了封裝模塊的方法,實現語法上相近,甚至於 requirejs 在後期也默默支持了 CMD 的寫法。咱們用一個例子,來說清楚這兩個規範之間最大的差別:依賴前置和就近依賴。node

AMD:webpack

// hello.js
define(function() {
    console.log('hello init');
    return {
        getMessage: function() {
            return 'hello';
        }
    };
});
// world.js
define(function() {
    console.log('world init');
});

// main
define(['./hello.js', './world.js'], function(hello) {
    return {
        sayHello: function() {
            console.log(hello.getMessage());
        }
    };
});

// 輸出
// hello init
// world init
複製代碼

CMD:es6

// hello.js
define(function(require, exports) {
    console.log('hello init');
    exports.getMessage = function() {
        return 'hello';
    };
});

// world.js
define(function(require, exports) {
    console.log('world init');
    exports.getMessage = function() {
        return 'world';
    };
});

// main
define(function(require) {
    var message;
    if (true) {
        message = require('./hello').getMessage();
    } else {
        message = require('./world').getMessage();
    }
});

// 輸出
// hello init
複製代碼

結論: CMD 的輸出結果中,沒有打印"world init"。可是,須要注意的是,CMD 沒有打印"world init"並是不 world.js 文件沒有加載。AMD 與 CMD 都是在頁面初始化時加載完成全部模塊,惟一的區別就是就近依賴是當模塊被 require 時纔會觸發執行。web

requirejs 和 seajs 的具體實如今這裏就不展開闡述了,有興趣的同窗能夠到官網瞭解一波,畢竟如今使用 requirejs 和 seajs 的應該不多了吧。後端

3. CommonJS

 回到 CommonJS,寫過 NodeJS 的同窗對它確定不會陌生。CommonJS 定義了,一個文件就是一個模塊。在 node.js 的實現中,也給每一個文件賦予了一個 module 對象,這個對象包括了描述當前模塊的全部信息,咱們嘗試打印 module 對象。瀏覽器

// index.js
console.log(module);

// 輸出
{
    id: '/Users/x/Documents/code/demo/index.js',
    exports: {},
    parent: { module }, // 調用該模塊的模塊,能夠根據該屬性查找調用鏈
    filename: '/Users/x/Documents/code/demo/index.js',
    loaded: false,
    children: [...],
    paths: [...]
}
複製代碼

也就是說,在 CommonJS 裏面,模塊是用對象來表示。咱們經過「循環加載」的例子進行來加深瞭解。

// a.js
exports.x = 'a1';
console.log('a.js ', require('./b.js').x);
exports.x = 'a2';

//b.js
exports.x = 'b1';
console.log('b.js ', require('./a.js').x);
exports.x = 'b2';

//main
console.log('index.js', require('./a.js').x);

// 輸出
b.js  a1
a.js  b2
index.js  a2
複製代碼

咱們的理論依據是模塊對象,根據該依據咱們進行以下分析。

一、 a.js準備加載,在內存中生成module對象moduleA
二、 a.js執行exports.x = 'a1'; 在moduleA的exports屬性中添加x
三、 a.js執行console.log('a.js', require('./b.js').x); 檢測到require關鍵字,開始加載b.js,a.js執行暫停
四、 b.js準備加載,在內存中生成module對象moduleB
五、 b.js執行exports.x = 'b1'; 在moduleB的exports屬性中添加x
六、 b.js執行console.log('b.js', require('./a.js').x); 檢測到require關鍵字,開始加載a.js,b.js執行暫停
七、 檢測到內存中存在a.js的module對象moduleA,因而能夠將第6步當作console.log('b.js', moduleA.x); 在第二步中moduleA.x賦值爲a1,因而輸出b.js, a1
八、 b.js繼續執行,exports.x = 'b2',改寫moduleBexports的x屬性
九、 b.js執行完成,回到a.js,此時同理能夠將第3步當作console.log('a.js', modulerB.x); 輸出了a.js, b2
十、 a.js繼續執行,改寫exports.x = 'a2'
十一、 輸出index.js a2
複製代碼

至此,「CommonJS 的模塊,是一個對象。」這個概念大夥兒應該能理解吧?

回到這個例子,例子裏面還出現了一個保留字 exports。其實 exports 是指向 module.exports 的一個引用。舉個例子能夠說明他們兩個之間的關係。

const myFuns = { a: 1 };
let moduleExports = myFuns;
let myExports = moduleExports;

// moduleExports 從新指向
moduleExports = { b: 2 };
console.log(myExports);
// 輸出 {a : 1}

// 也就是說在module.exports被從新複製時,exports與它的關係就gg了。解決方法就是從新指向
myExports = modulerExports;
console.log(myExports);
// 輸出 { b: 2 }
複製代碼

4. ES6 module

對 ES6 有所瞭解的同志們應該都清楚,web 前端模塊化在 ES6 以前,並非語言規範,不像是其餘語言 java、php 等存在命名空間或者包的概念。上文說起的 AMD、CMD、CommonJS 規範,都是爲了基於規範實現的模塊化,並不是 JavaScript 語法上的支持。 咱們先簡單的看一個 ES6 模塊化寫法的例子:

// a.js
export const a = 1;

// b.js
export const b = 2;

// main
import { a } from './a.js';
import { b } from './b.js';
console.log(a, b);
//輸出 1 2
複製代碼

emmmm,沒錯,export 保留字看起來是否是和 CommonJS 的 exports 有點像?咱們嘗試  下從保留字對比 ES6 和 CommonJS。

保留字 CommonJS ES6
require 支持 支持
export / import 不支持 支持
exports / module.exports 支持 不支持

好吧,除了 require 兩個均可以用以外,其餘實際上仍是有明顯差異的。那麼問題來了,既然 require 兩個均可以用,那這兩個在 require 使用上,有差別嗎?

咱們先對比下 ES6 module 和 CommonJS 之間的差別。

 模塊輸出 加載方式
CommonJS 值拷貝 對象
ES6 引用(符號連接) 靜態解析

又多了幾個新穎的詞彙,咱們先經過例子來介紹一下值拷貝和引用的區別。

// 值拷貝 vs 引用

// CommonJS
let a = 1;
exports.a = a;
exports.add = () => {
    a++;
};

const { add, a } = require('./a.js');
add();
console.log(a); // 1

// ES6
export const a = 1;
export const add = () => {
    a++;
};

import { a, add } from './a.js';
add();
console.log(a); // 2
// 顯而易見CommonJS和ES6之間,值拷貝和引用的區別吧。
複製代碼

靜態解析,什麼是的靜態解析呢?區別於 CommonJS 的模塊實現,ES6 的模塊並非一個對象,而只是代碼集合。也就是說,ES6 不須要和 CommonJS 同樣,須要把整個文件加載進去,造成一個對象以後,才能知道本身有什麼,而是在編寫代碼的過程當中,代碼是什麼,它就是什麼。

PS:

  1. 目前各個瀏覽器、node.js 端對 ES6 的模塊化支持實際上並不友好,更多實踐同志們有興趣能夠本身搞一波。
  2. 在 ES6 中使用 require 字樣,靜態解析的能力將會丟失!

5. UMD

模塊化規範中還有一個 UMD 也不得不說起一下。什麼是 UMD 呢?

UMD = AMD + CommonJS
複製代碼

沒錯,UMD 就是這麼簡單。經常使用的場景就是當你封裝的模塊須要適配不一樣平臺(瀏覽器、node.js),例如你寫了一個基於 Date 對象二次封裝的,對於時間的處理工具類,你想推廣給負責前端頁面開發的 A 同窗和後臺 Node.js 開發的 B 同窗使用,你是否是就須要考慮你封裝的模塊,既能適配 Node.js 的 CommonJS 協議,也能適配前端同窗使用的 AMD 協議?

2、工具時代

1. webpack

webpack 興起以後,什麼 AMD、CMD、CommonJS、UMD,彷佛都變得不重要了。由於 webpack 的模塊化能力真的強。

webpack 在定義模塊上,能夠支持 CommonJS、AMD 和 ES6 的模塊聲明方式,換句話說,就是你的模塊若是是使用 CommonJS、AMD 或 ES6 的語法寫的,webpack 都支持!咱們看下例子:

//say-amd.js
define(function() {
 'use strict';
    return {
        sayHello: () => {
            console.log('say hello by AMD');
        }
    };
});
//say-commonjs.js
exports.sayHello = () => {
    console.log('say hello by commonjs');
};
//say-es6.js
export const sayHello = () => {
    console.log('say hello in es6');
};

//main
import { sayHello as sayInAMD } from './say-amd';
import { sayHello as sayInCommonJS } from './say-commonjs';
import { sayHello as sayInES6 } from './say-es6';

sayInAMD();
sayInCommonJS();
sayInES6();
複製代碼

不只如此,webpack 識別了你的模塊以後,能夠將其打包成 UMD、AMD 等等規範的模塊從新輸出。例如上文說起到的你須要把 Date 模塊封裝成 UMD 格式。只須要在 webpack 的 output 中添加 libraryTarget: 'UMD'便可。

2. more...

總結

回到開始咱們提出的問題,咱們嘗試使用一張圖彙總上文說起到的一溜模塊化相關詞彙。

總結

@Author: _Jay

相關文章
相關標籤/搜索