1. 程式人生 > >js 模組化規範(commonjs、AMD、ES6、CMD)

js 模組化規範(commonjs、AMD、ES6、CMD)

開發中最流行的 commonjs、AMD、ES6、CMD 規範。

參考資料:
https://mp.weixin.qq.com/s/MPEhWlS9KiIc9I6Of5GpOQ
http://www.ruanyifeng.com/blog/2015/05/commonjs-in-browser.html
http://foio.github.io/requireJS/
https://github.com/ZhiCYue/requireJs-analysis/blob/master/require.js

學習筆記 + 原理分析

理解原理後,網上再蒐集相關資料時,會對一些概念有更好的認識,比如:延時執行、立即執行、執行時、編譯時等等。
再比如:“AMD是提前執行,CMD是延遲執行。” ,“CMD 推崇依賴就近,AMD 推崇依賴前置” 等等。

commonjs

Node 應用由模組組成,採用 CommonJS 模組規範。每個檔案就是一個模組,有自己的作用域。在一個檔案裡面定義的變數、函式、類,都是私有的,對其他檔案不可見。在伺服器端,模組的載入是執行時同步載入的;在瀏覽器端,模組需要提前編譯打包處理。

特點:
  1. 所有程式碼都執行在模組作用域,不會汙染全域性作用域。
  2. 模組可以多次載入,但是隻會在第一次載入時執行一次,然後執行結果就被快取了,以後再載入,就直接讀取快取結果。要想讓模組再次執行,必須清除快取。
  3. 模組載入的順序,按照其在程式碼中出現的順序。

基本語法:

  1. 暴露模組:module.exports = value 或 exports.xxx = value;
  2. 引入模組:require(xxx), 如果是第三方模組,xxx 為模組名;如果是自定義模組,xxx 為模組檔案路徑。

執行

  1. 服務端:node app.js
  2. 瀏覽器:藉助 Browserify。參考: browserify js/src/app.js -o js/dist/bundle.js

記錄點 1 CommonJS 模組的載入機制是,輸入的是被輸出的值的拷貝。也就是說,一旦輸出一個值,模組內部的變化就影響不到這個值。

// lib.js
var counter = 3;
function incCounter() {
 counter++;
}
module.exports = {
 counter: counter,
 incCounter: incCounter,
};
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;

console.log(counter);  // 3
incCounter();
console.log(counter); // 3

記錄點 2 瀏覽器用不支援 CommonJS 格式。要想讓瀏覽器用上這些模組,必須轉換格式。

瀏覽器不相容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 

上面程式碼向一個立即執行函式提供 module 和 exports 兩個外部變數,模組就放在這個立即執行函式裡面。模組的輸出值放在 module.exports 之中,這樣就實現了模組的載入。

Browserify 的實現

請看一個例子,main.js 模組載入 foo.js 模組。

// foo.js
module.exports = function(x) {
  console.log(x);
};

// main.js
var foo = require("./foo");
foo("Hi");

使用下面的命令,就能將main.js轉為瀏覽器可用的格式。

$ browserify main.js > compiled.js

browser 轉換後的compiled.js 原始碼:

(function () {
  function r(e, n, t) {
    function o(i, f) {
      if (!n[i]) {
        if (!e[i]) {
          var c = "function" == typeof require && require;
          if (!f && c) return c(i, !0);
          if (u) return u(i, !0);
          var a = new Error("Cannot find module '" + i + "'"); 
          throw a.code = "MODULE_NOT_FOUND", a
        }
        var p = n[i] = { exports: {} };
        e[i][0].call(p.exports, function (r) {
          var n = e[i][1][r];
          return o(n || r)
        }, p, p.exports, r, e, n, t)
      }
      return n[i].exports
    }
    for (var u = "function" == typeof require && require, i = 0; i < t.length; i++) o(t[i]);
    return o
  }
  return r
})()({
  1: [function (require, module, exports) {
    // foo.js
    module.exports = function (x) {
      console.log(x);
    };

  }, {}], 2: [function (require, module, exports) {
    // main.js
    var foo = require("./foo.js");
    foo("Hi");
  }, { "./foo.js": 1 }]
}, {}, [2]);

compiled.js 在html檔案中引入,瀏覽器開啟,即可看到控制檯輸出 Hi。

重構後的compiled.js 程式碼如下:(方便理解)

/** 模組一 */
var module1 = function (require, module, exports) {
  // foo.js
  module.exports = function (x) {
    console.log(x);
  };
}

/** 模組二 */
var module2 = function (require, module, exports) {
  // main.js
  var foo = require("./foo.js");
  foo("Hi");
}

/** 將模組放置陣列中,並儲存對應依賴 */
var rObject = { 
  1: [ module1, {} ],  
  2: [ module2, { "./foo.js": 1 } ] 
}

/** 相同依賴的進行快取 */
var cObject = {};

/** 定義入口js 模組索引 */
var mArr = [ 2 ];

/**
 * run 函式
 * 注:commonjs 的核心
 * @param {Object} relation 
 * @param {Object} cache
 * @param {*} t 
 */
function run(relation, cache, t) {

  function schedule(i, flag) {
    if (!cache[i]) {
      if (!relation[i]) {
        // 如果require 存在,並且型別為function
        var _require = "function" == typeof require && require;
        // 執行require 方法,執行依賴的js 指令碼
        if (!flag && _require) return _require(i, !0);
        if (_out_require) return _out_require(i, !0);

        // 丟擲異常
        var err = new Error("Cannot find module '" + i + "'"); 
        err.code = "MODULE_NOT_FOUND";
        throw err;
      }

      // 為每個模組定義一個快取物件
      var _module = cache[i] = { exports: {} };
      
      relation[i][0].call(_module.exports, function (path) {
        // 獲取path 指令碼在relation 中的序號
        var num = relation[i][1][path];
        return schedule(num || path)
      }, _module, _module.exports, run, relation, cache, t)
    }

    return cache[i].exports;
  }

  for (var _out_require = "function" == typeof require && require, i = 0; i < t.length; i++) {
    schedule(t[i]);
  }

  return schedule;
}

// 執行
run(rObject, cObject, mArr);

至此browserify 的原理一目瞭然。
但是,browserify 不能在瀏覽器中使用,但按照上述解析後的程式碼的方式編寫模組,則可以在瀏覽器端使用commonjs。其他方式的瀏覽器端commonjs,可以學習阮老師的 tiny-browser-require .

node 實現

node中的require() 方法原始碼解讀:http://www.ruanyifeng.com/blog/2015/05/require.html

AMD

CommonJS 規範載入模組是同步的,也就是說,只有載入完成,才能執行後面的操作。AMD 規範則是非同步載入模組,允許指定回撥函式。

由於 Node.js 主要用於伺服器程式設計,模組檔案一般都已經存在於本地硬碟,所以載入起來比較快,不用考慮非同步載入的方式,所以 CommonJS 規範比較適用。但是,如果是瀏覽器環境,要從伺服器端載入模組,這時就必須採用非同步模式,因此瀏覽器端一般採用 AMD 規範。此外 AMD 規範比 CommonJS 規範在瀏覽器端實現要來著早。

基本語法

定義暴露模組:

// 定義沒有依賴的模組
define(function(){
  return 模組
})

// 定義有依賴的模組
define(['module1', 'module2'], function(m1, m2){
  return 模組
})

引入使用模組:

require(['module1', 'module2'], function(m1, m2){
  使用 m1/m2
})

主要的js 庫:require.js

官網: http://www.requirejs.cn/
github : https://github.com/requirejs/requirejs

使用方法

在 index.html 引入

 < script data-main="js/main" src="js/libs/require.js">< /script>
比較

如果不使用amd 的方式,使用通常的閉包方式引入js,如果js指令碼多並且相互依賴複雜時,程式碼可能會是這樣子:

// index.html 檔案
<div><h1>Modular Demo 1: 未使用 AMD(require.js)</h1></div>
<script type="text/javascript" src="js/modules/dataService.js"></script>
<script type="text/javascript" src="js/modules/alerter.js"></script>
// ... 更多的指令碼引入
<script type="text/javascript" src="js/main.js"></script>

弊端:引入順序要要求嚴格,js請求次數多。

原始碼解析

原始碼:https://cdn.bootcss.com/require.js/2.3.6/require.js
相關資料:http://foio.github.io/requireJS/

分析require原始碼前,先了解一下指令碼的非同步載入相關:

並行的下載指令碼

(1) XHR eval

瀏覽器原理中知道,瀏覽器解析script標籤的js指令碼是同步的,即會阻塞dom的渲染過程。如以下程式碼:

// a.js
alert(document.getElementById('div'))
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Document</title>
</head>
<body>
    <!-- 方式 1 -->
    <script>
      var XMLHttpReq = new XMLHttpRequest();
      XMLHttpReq.onreadystatechange = function(){
        if (XMLHttpReq.readyState == 4) {
          if (XMLHttpReq.status == 200) {
            var text = XMLHttpReq.responseText;
            eval(text)
          }
        }
      }; 
      XMLHttpReq.open("get", 'a.js', true);
      XMLHttpReq.send();
    </script>

    <!-- 方式 2 -->
    <!-- <script src='a.js'></script> -->

    <div id="div">Hello.</div>
</body>
</html>

通過靜態服務訪問index.html, 可知方式1 是非同步方式,在請求a.js 指令碼時不阻塞dom的渲染,模擬請求延時長一些,則可以拿到div 元素;方式 2 是同步方式,a.js指令碼在執行時,頁面是還沒有id為"div"元素的,因此alert的內容始終為null。

xhr eval方式缺點: 不支援跨域請求。

(2) script dom element

我們也可以直接在瀏覽器中插入script dom節點。如下程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Document</title>
</head>
<body>
	<!-- 方式 1 -->
	<!-- 方式 2 -->
	
    <!-- 方式 3 -->
    <script>
      var scriptElem = document.createElement('script');
      scriptElem.src = 'http://127.0.0.1:9006/a.js';
      document.getElementsByTagName('head')[0].appendChild(scriptElem);
    </script>

    <div id="div">Hello.</div>
</body>
</html>

啟動本機靜態服務訪問index.html, 地址:http://localhost:9006/index.html 可以看到結果,指令碼的載入執行不阻塞dom的渲染(由於頁面的div比較簡單瀏覽器渲染很快,a.js指令碼中的alert 測試時都能夠獲取到渲染後的div,而a.js是在載入完後就開始執行的

優點:支援跨域請求。
缺點:需要工程師自己在程式碼層面實現執行順序的控制。

記錄點 3 requir.js 就是通過script dom element 的方式實現的。

(3) document write script tag

這種方式是使用document.write 方法:

document.write("<script type='text/javascript' src='A.js'></script>");

優點:
scritp Tag可以保證多個指令碼並行載入。
可以保證指令碼按文件中出現的順序執行。
 
缺點:
會阻塞其他資源並行下載。

(4) defer和async屬性

async="async"不會阻塞其他資源,但是無法保證指令碼的執行順序。defer="defer"阻塞其他資源的載入,並且可以保證指令碼的執行順序,但是要到頁面解析完成後才開始執行指令碼。

指令碼的執行順序

當外部指令碼按常規方式載入時,它會阻塞行內指令碼的執行,可以保證順序。但是指令碼通過上述的幾種方式非同步載入時,就無法保證行內指令碼和非同步指令碼之間的順序。

保證行內指令碼和外部指令碼的執行順序:

(1) onlode事件
新增script dom節點時,監聽載入事件:

//行內函式
function callback(){
    Console.log(‘calllback’);
}

//非同步載入函式
function loadScript(url, callback){
    var script = document.createElement ("script")
    script.type = "text/javascript";
    if (script.readyState){ //IE
        script.onreadystatechange = function(){
            if (script.readyState == "loaded" || script.readyState == "complete"){
                script.onreadystatechange = null;
                callback();
            }
        };
    } else { //Others
        script.onload = function(){
            callback();
        };
    }
    script.src = url;
    document.getElementsByTagName("head")[0].appendChild(script);
}

//控制行內指令碼和外部指令碼的執行順序
loadScript('a.js',callback);

(2) 定時器
通過定時檢查外部指令碼是否載入完成(即檢查對應變數是否存在):

<script src="MyJs.js"></script>
<script>
function callback(){
}

function checkMyJs(){
    if(undefined===typeof(MyJs)){
        setTimeout(checkMyJs, 300)
    }else{
        callback();
    }
}
</script>

特點:
onload 和定時器的方式缺點:多個指令碼的執行順序不好控制。

多個外部指令碼之間的執行順序

(1) 同域指令碼
對於同域中的多個外部指令碼,可以使用XHR的方式載入指令碼,並通過一個佇列來控制指令碼的執行順序。

// a.js
console.log('a');
// b.js
console.log('b');
// c.js
console.log('c');
<!DOCTYPE html>
<html lang="en">

<head>
  <title>Document</title>
</head>

<body>
  <script>
    var ScriptLoader = ScriptLoader || {}
    ScriptLoader.Script = {
      //指令碼佇列
      queueScripts: [],

      loadScriptXhrInjection: function (url, onload, bOrder) {
        var iQ = ScriptLoader.Script.queueScripts.length;
        if (bOrder) {
          var qScript = { response: null, onload: onload, done: false };
          ScriptLoader.Script.queueScripts[iQ] = qScript;
        }
        var xhrObj = ScriptLoader.Script.getXHROject();
        xhrObj.onreadystatechange = function () {
          if (xhrObj.readyState == 4) {
            //有順序要求的指令碼需要新增的佇列,按新增順序執行
            if (bOrder) {
              //有順序要求的指令碼需要設定載入和執行狀態
              ScriptLoader.Script.queueScripts[iQ].response = xhrObj.responseText;
              //執行指令碼佇列
              ScriptLoader
            
           

相關推薦

js 模組規範commonjsAMDES6CMD

開發中最流行的 commonjs、AMD、ES6、CMD 規範。 參考資料: https://mp.weixin.qq.com/s/MPEhWlS9KiIc9I6Of5GpOQ http://www.ruanyifeng.com/blog/2015/05/commonjs-in-

再談 JS中的模組規範CommonJSAMDCMD來自玉伯的seajs分析

                隨著網際網路的飛速發展,前端開發越來越複雜。本文將從實際專案中遇到的問題出發,講述模組化能解決哪些問題,以及如何使用 Sea.js 進行前端的模組化開發。惱人的命名衝突我們從一個簡單的習慣出發。我做專案時,常常會將一些通用的、底層的功能抽象出來,獨立成一個個函式,比如funct

JS模組規範AMD/CMD/CommonJS

一、模組化規範的由來 隨著網頁的複雜化,javascript程式碼也相應變得龐大起來,程式碼之間的分工合作就尤為重要了,這時候有了模組,我們就可以只關注自己業務的核心邏輯,其他想要實現的功能直接載入他人的模組便足夠了。考慮到模組的統一,便有了模組規範化。接下來

淺析JS中的模塊規範CommonJSAMDCMD http://www.2cto.com/kf/201411/348276.html

cpu 重要 mat 只有一個 targe () actor cti 最重要的 如果你聽過js模塊化這個東西,那麽你就應該聽過或CommonJS或AMD甚至是CMD這些規範咯,我也聽過,但之前也真的是聽聽而已。 現在就看看吧,這些規範到底是啥東西,幹嘛的。

理解JS中的模塊規範CommonJSAMDCMD

site 繼續 arr 包管理器 color sea 文件依賴 避免 說我   隨著互聯網的飛速發展,前端開發越來越復雜。本文將從實際項目中遇到的問題出發,講述模塊化能解決哪些問題,以及如何使用 Sea.js 進行前端的模塊化開發。 惱人的命名沖突   我們從一個簡單的習慣

前端模組IIFE,commonjsAMD,UMD,ES6 Module規範超詳細講解

[TOC] ## 為什麼前端需要模組化 在沒有模組化的時候,多個指令碼引入頁面,會造成諸多問題,比如: - 多人協同開發的時候,系統中可能會引入很多js指令碼,這些js會定義諸多全域性變數,這時候很容易出現變數名覆蓋的問題 ```html

一覽js模組:從CommonJSES6

本文由雲+社群發表 模組化是指把一個複雜的系統分解到一個一個的模組。 模組化開發的優點: (1)程式碼複用,讓我們更方便地進行程式碼管理、同時也便於後面程式碼的修改和維護。 (2)一個單獨的檔案就是一個模組,是一個單獨的作用域,只向外暴露特定的變數和函式。這樣可以避免汙染全域性變數,減少變數

JS 模組規範

在我們最初寫程式碼的時候,引入JS檔案用script標籤來引入,並且在引入多個JS檔案時,當前檔案所依賴的JS檔案必須放在前面。也就存在一個順序的問題,而且這是由開發者去判斷和把控的。而現在前端專案越來越複雜,難免會出現很多很多script標籤引入JS,這無論對

Js模組規範理解

export const obj = { test1: '' } export const test = '' exrpot class Test { constuctor() { } } 或者是下邊的寫法 var name = "name" var age = "age" e

ES6中的模組規範

注意:ES6 的模組自動採用嚴格模式,不管你有沒有在模組頭部加上"use strict";。模組功能主要由兩個命令構成:export和import。export命令用於規定模組的對外介面,import命令用於輸入其他模組提供的功能。export命令一個模組就是一個獨立的檔案。

JS模組程式設計之AMD規範

隨著網站逐漸變成"網際網路應用程式",嵌入網頁的Javascript程式碼越來越龐大,越來越複雜。 網頁越來越像桌面程式,需要一個團隊分工協作、進度管理、單元測試等等......開發者不得不使用軟體工程的方法,管理網頁的業務邏輯。 JavaScript模組化程式設

詳解AMDCommonJS和UMD模組規範

開發的時候,我們經常會把某些功能封裝成可複用的模組。模組封裝了功能,並且對外暴露一個API。隨著Node.js的誕生和發展,JavaScript可以在服務端執行,同時客戶端應用也越來越流行,JavaScript界產生了對優秀和健壯模組系統的需求。在Java

JS模組程式設計之AMD模型實現原理Requirejs瀏覽器模型,nodejs伺服器模型

官方引數手冊:https://requirejs.org/docs/api.html     /** * require使用指南! * 第一步:建立一個符合Require CMD模組化的標準目錄結構,去官方檢視! * 第二步:在html檔案中指定主js檔

模組規範之commenJSAMDCMDES6

commenJS(Node.js基於commenJS規範編寫) 理解 每個檔案都可以當作一個模組 在伺服器端(Node):模組的載入是在執行時同步載入的 在瀏覽器端(Browserify):模組需要提前編譯打包處理 基本語法 暴露模組(暴露出

js模組程式設計之徹底弄懂CommonJSAMD/CMD

先回答我:為什麼模組很重要?   答:因為有了模組,我們就可以更方便地使用別人的程式碼,想要什麼功能,就載入什麼模組。但是,這樣做有一個前提,那就是大家必須以同樣的方式編寫模組,否則你有你的寫法,我有我的寫法,豈不是亂了套! 於是下面三個模組規範出來了,這篇文章也出來了(拼出來的 {捂臉笑})。 &

require-js-模組 CMD AMD

模組化的標準有了模組,我們就可以更方便地使用別人的程式碼,想要什麼功能,就載入什麼模組。這樣做有一個前提,那就是大家必須以同樣的方式編寫模組,否則你有你的寫法,我有我的寫法,豈不是亂了套!  CommonJS:是一個模組化的標準,Node.js在使用的模組化標準。適用與後端開發的標準。AMD(As

JS模組(一):CommonJS

模組化的規範:         目前有四種:             1.CommonJS      &nbs

JS模組開發規範

JS模組化開發規範,以下介紹三種 commonJS規範(Nodejs模組系統遵循此規範,適用於服務端) 1、 規範定義 CommonJS規範規定,一個檔案就是一個模組,用module變數代表當前模組。 Node在其內部提供一個Module的構建函式。所有模組都是Module的例項 2、 module.ex

[前端]關於JS模組/AMD/CMD/UMD及CSS的BEM

工作上接觸到的模組化都是比較主流的ESM和commonJS CSS上通用的是BEM 這裡主要是總結給自己看的 ESM(ES6 Module) 一個檔案一個模組 基本是webpack結合vue最常用的東西了 引入import,暴露用export import re

js模組AMDCMD的區別

最近在研究cmd和amd,在網上看到一篇不錯的文章,整理下看看。  在JavaScript發展初期就是為了實現簡單的頁面互動邏輯,寥寥數語即可;如今CPU、瀏覽器效能得到了極大的提升,很多頁面邏輯遷移到了客戶端(表單驗證等),隨著web2.0時代的到來,Ajax技術得到