談談Js前端模塊化規範

拋出問題:

  • 在開發中在導入模塊時常用requireimport
  • 導出模塊時使用module.exports/exports或者export/export default;
  • 有時候爲了引用一個模塊會使用require奇怪的是也可使用import????它們之間有何區別呢?
因而有了菜鳥解惑的搜嘍過程。。。。。。

追溯根源,來到Js模塊化規範

一、CommonJS規範(同步加載模塊)

  • 容許模塊經過require方法來同步加載所要依賴的其餘模塊,而後經過exports或module.exports來導出須要暴露的接口。
  • 使用方式:
// 導入
require("module");
require("../app.js");
// 導出
exports.getStoreInfo = function() {};
module.exports = someValue;
    • 優勢:html

      1. 簡單容易使用
      2. 服務器端模塊便於複用
    • 缺點:node

      1. 同步加載方式不適合在瀏覽器環境中使用,同步意味着阻塞加載,瀏覽器資源是異步加載的
      2. 不能非阻塞的並行加載多個模塊
    • 爲何瀏覽器不能使用同步加載,服務端能夠?react

      • 由於模塊都放在服務器端,對於服務端來講模塊加載時
      • 而對於瀏覽器端,由於模塊都放在服務器端,加載的時間還取決於網速的快慢等因素,若是須要等很長時間,整個應用就會被阻塞。
      • 所以,瀏覽器端的模塊,不能採用"同步加載"(CommonJs),只能採用"異步加載"(AMD)。
    • 參照CommonJs模塊表明node.js的模塊系統

    AMD(異步加載模塊)

    • 採用異步方式加載模塊,模塊的加載不影響後面語句的運行。全部依賴模塊的語句,都定義在一個回調函數中,等到加載完成以後,回調函數才執行。
    • 使用實例:
    // 定義
    define("module", ["dep1", "dep2"], function(d1, d2) {...});
    // 加載模塊
    require(["module", "../app"], function(module, app) {...});
    • 加載模塊require([module], callback);第一個參數[module],是一個數組,裏面的成員就是要加載的模塊;第二個參數callback是加載成功以後的回調函。
    • 優勢:git

      1. 適合在瀏覽器環境中異步加載模塊
      2. 能夠並行加載多個模塊
    • 缺點:github

      1. 提升了開發成本,代碼的閱讀和書寫比較困難,模塊定義方式的語義不暢
      2. 不符合通用的模塊化思惟方式,是一種妥協的實現
    • 實現AMD規範表明require.js
    RequireJS對模塊的態度是預執行。因爲 RequireJS 是執行的 AMD 規範, 所以全部的依賴模塊都是先執行;即RequireJS是預先把依賴的模塊執行,至關於把require提早了
    • RequireJS執行流程:
    1. require函數檢查依賴的模塊,根據配置文件,獲取js文件的實際路徑
    2. 根據js文件實際路徑,在dom中插入script節點,並綁定onload事件來獲取該模塊加載完成的通知。
    3. 依賴script所有加載完成後,調用回調函數

    CMD規範(異步加載模塊)

    • CMD規範和AMD很類似,簡單,並與CommonJS和Node.js的 Modules 規範保持了很大的兼容性;在CMD規範中,一個模塊就是一個文件。
    • 定義模塊使用全局函數define,其接收 factory 參數,factory 能夠是一個函數,也能夠是一個對象或字符串;
    • factory 是一個函數,有三個參數,function(require, exports, module):segmentfault

      1. require 是一個方法,接受模塊標識做爲惟一參數,用來獲取其餘模塊提供的接口:require(id)
      2. exports 是一個對象,用來向外提供模塊接口
      3. module 是一個對象,上面存儲了與當前模塊相關聯的一些屬性和方法
    • 實例:
    define(function(require, exports, module) {
      var a = require('./a');
      a.doSomething();
      // 依賴就近書寫,何時用到何時引入
      var b = require('./b');
      b.doSomething();
    });
    • 優勢:
    1. 依賴就近,延遲執行
    2. 能夠很容易在 Node.js 中運行
    • 缺點:
    1. 依賴 SPM 打包,模塊的加載邏輯偏重
    • 實現表明庫sea.js:SeaJS對模塊的態度是懶執行, SeaJS只會在真正須要使用(依賴)模塊時才執行該模塊

    AMD 與 CMD 的區別

    1. 對於依賴的模塊,AMD 是提早執行,CMD 是延遲執行。不過 RequireJS 從2.0開始,也改爲了能夠延遲執行(根據寫法不一樣,處理方式不一樣)。CMD 推崇 as lazy as possible.
    2. AMD推崇依賴前置;CMD推崇依賴就近,只有在用到某個模塊的時候再去require。
    // AMD
    define(['./a', './b'], function(a, b) {  // 依賴必須一開始就寫好  
       a.doSomething()    
       // 此處略去 100 行    
       b.doSomething()    
       ...
    });
    // CMD
    define(function(require, exports, module) {
       var a = require('./a')   
       a.doSomething()   
       // 此處略去 100 行   
       var b = require('./b') 
       // 依賴能夠就近書寫   
       b.doSomething()
       // ... 
    });

    UMD

    • UMD是AMD和CommonJS的糅合
    • AMD 以瀏覽器第一原則發展異步加載模塊。
    • CommonJS 模塊以服務器第一原則發展,選擇同步加載,它的模塊無需包裝。
    • UMD先判斷是否支持Node.js的模塊(exports)是否存在,存在則使用Node.js模塊模式;在判斷是否支持AMD(define是否存在),存在則使用AMD方式加載模塊。
    (function (window, factory) {
        if (typeof exports === 'object') {
        
            module.exports = factory();
        } else if (typeof define === 'function' && define.amd) {
        
            define(factory);
        } else {
        
            window.eventUtil = factory();
        }
    })(this, function () {
        //module ...
    });

    ES6模塊化

    • ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。
    • ES6 模塊設計思想:儘可能的靜態化、使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量(CommonJS和AMD模塊,都只能在運行時肯定這些東西)。數組

      • 使用方式:
    // 導入
    import "/app";
    import React from 「react」;
    import { Component } from 「react」;
    // 導出
    export function multiply() {...};
    export var year = 2018;
    export default ...
    ...
    • 優勢:
    1. 容易進行靜態分析
    2. 面向將來的 EcmaScript 標準
    • 缺點:
    1. 原生瀏覽器端尚未實現該標準
    2. 全新的命令字,新版的 Node.js才支持。

    回到問題「require與import的區別」

    • require使用與CommonJs規範,import使用於Es6模塊規範;因此二者的區別實質是兩種規範的區別;
    • CommonJS:
    1. 對於基本數據類型,屬於複製。即會被模塊緩存;同時,在另外一個模塊能夠對該模塊輸出的變量從新賦值。
    2. 對於複雜數據類型,屬於淺拷貝。因爲兩個模塊引用的對象指向同一個內存空間,所以對該模塊的值作修改時會影響另外一個模塊。
    3. 當使用require命令加載某個模塊時,就會運行整個模塊的代碼。
    4. 當使用require命令加載同一個模塊時,不會再執行該模塊,而是取到緩存之中的值。也就是說,CommonJS模塊不管加載多少次,都只會在第一次加載時運行一次,之後再加載,就返回第一次運行的結果,除非手動清除系統緩存。
    5. 循環加載時,屬於加載時執行。即腳本代碼在require的時候,就會所有執行。一旦出現某個模塊被"循環加載",就只輸出已經執行的部分,還未執行的部分不會輸出。
    • ES6模塊
    1. ES6模塊中的值屬於【動態只讀引用】。
    2. 對於只讀來講,即不容許修改引入變量的值,import的變量是隻讀的,不管是基本數據類型仍是複雜數據類型。當模塊遇到import命令時,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裏面去取值。
    3. 對於動態來講,原始值發生變化,import加載的值也會發生變化。不管是基本數據類型仍是複雜數據類型。
    4. 循環加載時,ES6模塊是動態引用。只要兩個模塊之間存在某個引用,代碼就可以執行。
    • 最後:require/exports 是必要通用且必須的;由於事實上,目前你編寫的 import/export 最終都是編譯爲 require/exports 來執行的。
    • 參考
    1. AMD與CMD規範詳解
    2. CommonJS模塊和ES6模塊的區別
    3. Js模塊規範
    4. 理解Js模塊化
    5. Require與import的區別
    「積跬步、行千里」—— 不時的分享更新中~,喜歡留下個贊哦!
    相關文章
    相關標籤/搜索