前端入門22-講講模塊化

来源:https://www.cnblogs.com/dasusu/archive/2018/12/11/10105433.html
-Advertisement-
Play Games

聲明 本篇內容梳理自以下來源: "Github:smyhvae/web" "JavaScript模塊化開發的演進歷程" "JavaScript模塊化七日談" "ES6:Module 的載入實現" "CommonJS規範" 感謝各位大佬的分享,解惑了很多。 正文 模塊化 現在回過頭來想想,也許選擇以《 ...


聲明

本篇內容梳理自以下來源:

感謝各位大佬的分享,解惑了很多。

正文-模塊化

現在回過頭來想想,也許選擇以《JavaScript權威指南》一書來作為入門有些不好,因為這本書畢竟是很早之前的,書中所講的思想、標準也基本都只是 ES5 及那時代的相關技術。

這也就導致了,在書中看到的很多例子,雖然覺得所用到的思想很奇妙,比如臨時命名空間之類的處理,但其實,有些技術到現在已經有了更為強大的技術替代了。

就像這篇要講的模塊化,目前,以我看到的各資料後,所收穫的知識是,大概有四種較為常用且熱門的模塊化技術,也許還有更新的技術,也許還有我不知道的技術,無所謂,慢慢來,這篇的內容已經夠我消化了。

目前四種模塊化技術:

  • CommonJS規範&node.js
  • AMD規範&Require.js
  • CMD規範&Sea.js
  • ES6標準

前面是規範,規範就是類似於 ES5,ES6 只是提出來作為一種標準規範,而不同規範有具體的實現,比如 nodeJS 實現了 CommonJS 規範。

模塊化歷程

在聲明部分中的第二、第三鏈接里那兩篇,以時間線介紹了模塊化的相關技術的發展歷程,我覺得很有必要一看,對於掌握和理解目前的模塊化技術挺有幫助的。

這裡,就稍微摘抄其中一些內容,詳細內容還是需要到原文閱讀。

1.全局變數、全局函數(1999年)

這時候的多個 js 腳本文件之間,直接通過全局變數或全局函數的方式進行通信,這種方式叫做:直接定義依賴。

雖然做的好一些的會對這些 js 文件做一些目錄規劃,將資源歸類整理,但仍無法解決全局命名空間被大量污染,極其容易導致變數衝突問題。

2.對象作為命名空間(2002年)

為瞭解決遍地的全局變數問題,這時候提出一種命名空間模式的思路,即將本要定義成全局變數、全局函數的這些全都作為一個對象的屬性存在,這個對象的角色充當命名空間,不同模塊的 JS 文件中通過訪問這個對象的屬性來進行通信。

3.立即執行的函數作為臨時命名空間 + 閉包(2003年)

雖然提出利用一個對象來作為命名空間的思路,一定程度解決了大量的全局變數的問題,但仍舊存在很多局限,比如沒有模塊的隱藏性,所以針對這些問題,這時候又新提出一種思路:利用立即執行的函數來作為臨時命名空間,這樣就可以避免污染全局命名空間,同時,結合閉包特性,來實現隱藏細節,只對外暴露指定介面。

雖然這種思路,解決了很多問題,但仍舊有一些局限,比如,缺乏管理者,什麼意思,就是說,在前端里,開發人員得手動將不同 JS 腳本文件按照它們之間的依賴關係,以被依賴在前面的順序來手動書寫 <script> 載入這些文件。

也就是不同 <script> 的前後順序實際上表示著它們之間的依賴關係,一旦文件過多,將會很難維護,這是上述方案都存在的問題。

4.動態創建 <script> 工具(2009年)

針對上述問題,也就衍生出了一些載入 js 文件的工具,先看個例子:

$LAB.script("greeting.js").wait()
    .script("x.js")
    .script("y.js").wait()
    .script("run.js");

LAB.js 這類載入工具的原理實際上是動態的創建 <script>,達到作為不同 JS 腳本文件的管理者作用,來決定 JS 文件的載入和執行順序。

雖然我沒用過這種工具,但我覺得它的局限還是有很多,其實就是將開發人員本來需要手動書寫在 HTML 文檔里的 <script> 代碼換成寫在 JS 文件中,不同 JS 文件之間的依賴關係仍舊需要按照前後順序手動維護。

5.CommonJS規範&node.js(2009年)

中間跳過了一些過程,比如 YUI 的沙箱模式等,因為不熟,想瞭解更詳細的可以去原文閱讀。

當 CommonJS 規範出來時,模塊化算是進入了真正的革命,因為在此之前的探索,都是基於語言層面的優化,也就是利用函數特性、對象特性等來在運行期間模擬出模塊的作用,而從這個時候起,模塊化的探索就大量的使用了預編譯。

CommonJS 模塊化規範有如下特點:

  • 所有代碼都運行在模塊作用域,不會污染全局作用域。
  • 模塊可以多次載入,但是只會在第一次載入時運行一次,然後運行結果就被緩存了,以後再載入,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
  • 模塊載入的順序,按照其在代碼中出現的順序。

不同的模塊間的依賴關係通過 require 來控制,而每個模塊需要對外暴露的介面通過 exports 來決定。

由於 CommonJS 規範本身就只是為了服務端的 node.js 而考慮的,node.js 實現了 CommonJS 規範,所以運行在 node.js 環境中的 js 代碼可以使用 requireexports 這兩個命令,但在前端里,瀏覽器的 js 執行引擎並不認識 require 這些命令,所以需要進行一次轉換工作,後續介紹。

再來看看 require 命令的工作原理:

require 命令是 CommonJS 規範之中,用來載入其他模塊的命令。它其實不是一個全局命令,而是指向當前模塊的 module.require 命令,而後者又調用 Node 的內部命令 Module._load

Module._load = function(request, parent, isMain) {
  // 1. 檢查 Module._cache,是否緩存之中有指定模塊
  // 2. 如果緩存之中沒有,就創建一個新的Module實例
  // 3. 將它保存到緩存
  // 4. 使用 module.load() 載入指定的模塊文件,
  //    讀取文件內容之後,使用 module.compile() 執行文件代碼
  // 5. 如果載入/解析過程報錯,就從緩存刪除該模塊
  // 6. 返回該模塊的 module.exports
};

Module.prototype._compile = function(content, filename) {
  // 1. 生成一個require函數,指向module.require
  // 2. 載入其他輔助方法到require
  // 3. 將文件內容放到一個函數之中,該函數可調用 require
  // 4. 執行該函數
};

所以,其實 CommonJS 的模塊化規範之所以可以實現控製作用域、模塊依賴、模塊通信,其實本質上就是將模塊內的代碼都放在一個函數內來執行,這過程中會創建一個對象 Module,然後將模塊的相關信息包括對外的介面信息都添加到對象 Module 中,供其他模塊使用。

6.AMD規範&Require.js(2009年)

CommonJS 規範載入模塊是同步的,也就是說,只有載入完成,才能執行後面的操作。由於 Node.js 主要用於伺服器編程,模塊文件一般都已經存在於本地硬碟,所以載入起來比較快,不用考慮非同步載入的方式,所以CommonJS 規範比較適用。

但是,如果是瀏覽器環境,這種同步載入文件的模式就會導致瀏覽器陷入卡死狀態,因為網路原因是不可控的。所以,針對瀏覽器環境的模塊化,新提出了一種規範:AMD(Asynchronous Modules Definition)非同步模塊定義。

也就是說,對於 Node.js,對於服務端而言,模塊化規範就是按照 CommonJS 規範即可。

但對於瀏覽器,對於前端而言,CommonJS 不適用,需要看看 AMD 規範。

AMD 規範中定義:

  • 定義一個模塊時通過 define 命令,通過 return 聲明模塊對外暴露的介面
  • 依賴其他模塊時,通過 require 命令

而規範終歸只是規範,使用時還是需要有規範的具體實現,針對 AMD 規範,具體的實現是 Require.js,在前端里,如何基於 Require.js 來使用 AMD 規範的模塊化技術,後續介紹。

7.CMD規範&Sea.js(2013年)

CMD(Common Module Definition)也是專門針對瀏覽器、針對前端而提出的一種模塊化規範。它跟 AMD 規範都是用途都是一樣,用途解決前端里的模塊化技術。

但兩種規範各有各的優缺點,各有各的適用場景和局限性吧,我還沒使用過這兩種規範,所以無從比較,但網上關於這兩種規範比較的文章倒是不少。

CMD 規範中定義:

  • 使用 define 命令定義一個模塊,使用 exports 來暴露模塊對外的介面
  • 使用 require 來同步載入依賴的模塊,但也可使用 require.async 來非同步載入依賴的模塊

總之,雖然兩種規範都是用於解決前端里的模塊化技術,但實現的本質上還是有些不同,後續介紹。

對於 CMD 規範的具體實現是 Sea.js,前端里如果想使用 CMD 規範的模塊化技術,需要藉助 Sea.js。

8.ES6標準(2015年)

2015 年發佈的 ES6 標準規範中,新增了 Module 特性,也就是官方在 ES6 中,在語言本身層面上,添加了模塊的機制支持,讓 JavaScript 語言本身終於可以支持模塊特性了。

在 ES6 之前的各種方案,包括 CommonJS,AMD,CMD,本質上其實都是利用函數具有的本地變數的特性進行封裝從而模擬出模塊機制。也就是說,這些方案都是需要在運行期間,才會去動態的創建出某個模塊,並暴露模塊的相關介面。這種方案其實也存在一些局限性。

而 ES6 新增了模塊的機制後,在代碼的解析階段,就能夠確定模塊以及模塊對外的介面,而不用等到運行期。這種本質上的區別,在藉助開發工具寫代碼階段的影響很明顯就是,終於可以讓開發工具智能的提示依賴的模塊都對外提供了哪些介面。

但 ES6 由於是新增的特性,在支持方面目前好像還不是很理想,並不是所有環境都支持 ES6 的模塊機制好像,所以會看到某些大佬的文章里會介紹一些諸如:Babel、Browserify。

Babel 用於將 ES6 轉換為 ES5 代碼,好讓不支持 ES6 特性的環境可以運行 ES5 代碼。

Browserify 用於將編譯打包 js,處理 require 這些瀏覽器並不認識的命令。

上面只是簡單介紹了下模塊化的發展歷程,而且中間略過一些階段,想看詳細的可以去原文閱讀。下麵就具體介紹下,目前四個比較穩定的模塊技術:

CommonJS規範

由於 CommonJS 是針對服務端設計的模塊化規範,對於 Node.js 來說,一個 JS 文件就是一個模塊,所以並不需要類似 AMD 或 CMD 中的 define 來聲明一個模塊。這是我的理解。

exports

既然在 Node.js 中,每個 JS 文件就是一個模塊,那麼這個模塊文件內的變數都是對外隱藏的,外部無權訪問,只能訪問 exports 對外暴露的介面,如:

//module.js
var name = "dasu";
var wx = "dasuAndroidTv";
var getBlog = function() {
    return "http://www.cnblogs.com/dasusu/";
}
//以上變數和函數,外部都無權訪問,外部只能訪問到下麵通過 exports 暴露的介面
module.exports.name = name;
exports.getWx = () => wx;

模塊內,可以通過對 module.exports 或 exports 添加屬性來達到對外暴露指定介面的目的,當然,從程式上來說,你可以直接改變 module.exports 的指向,讓它指向一個新對象而不是在原本對象上添加屬性,這個就類似於對構造函數 prototype 屬性的修改。

但建議使用方式還是儘可能在 exports 對象上添加屬性。

如果有想探究它的原理的話,可以嘗試利用 Browserify 來轉換這段模塊代碼,看看最後生成的是什麼:

function(require,module,exports){
    //module.js
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    //以上變數和函數,外部都無權訪問,外部只能訪問到下麵通過 exports 暴露的介面
    module.exports.name = name;
    exports.getWx = () => wx;
}

雖然,對於 Node.js 來說,它其實對待每個 JS 文件,本質上,會將文件內的代碼都放於一個函數內,如果該模塊首次被其他模塊引用了,那麼就會去執行這個函數,也就是執行模塊內的代碼,由於函數本身有三個參數,其中有兩個分別是:module 和 exports,這也是內部為什麼可以直接通過 module.exports 或 exports 來操作的原因。

Module 對象是 Node.js 會為每個模塊文件創建的一個對象,模塊之間的通信,其實就是通過訪問每個 Module 對象的屬性來實現。

所以,說白了,CommonJS 模塊化技術的本質,其實就是利用了函數的局部作用域的特性來實現模塊作用域,然後結合一個對象作為命名空間的方式,來保存模塊內部需要對外暴露的信息方式。最後,通過 require 命令來組織各模塊之間的依賴關係,解決以前方案沒有管理者角色的局限,解決誰先載入誰後載入的問題。

require

每個 JS 文件其實都被當做一個模塊處理,也就是文件內的代碼都會被放入到一個函數內,那這個函數什麼時候執行呢?也就是說,模塊什麼時候應該被載入呢?

這就是由 require 命令決定,當某個模塊內使用了 require 命令去載入其他模塊,那麼這時候,被載入的模塊如果是首次被調用,它是沒有對應的 Module 對象的,所以會去調用它的函數,執行模塊內代碼,這個過程是同步的,這期間會完善這個模塊的 Module 對象信息。之後,其他模塊如果也引用了這個模塊,因為模塊內代碼已經被執行過了,已經存在對應的 Module 對象,所以此時就不會再重覆去載入這個模塊了,直接返回 Module 對象。

以上,基本也就是模塊間依賴的載入處理過程。而 require 命令用法很簡單:

//main.js
var module1 = require("./module");

module1.name;  //輸出=> dasu
module1.getWx(); //輸出 => dasuAndroidTv
//module1.getBlog(); //沒有許可權訪問

其實,Node.js 對 main.js 的處理也是認為它是個模塊,所以文件內的代碼也都放入一個函數內,還記得函數的第一個參數就是 require 麽,這也就是為什麼模塊內可以直接使用 require() 的原因,require 其實本質上是一個函數,具體的實現是 Node.js 的一個內置方法:Module._load(),主要工作上一節有介紹過了。

說得稍微嚴謹點,Node.js 其實才是作為各模塊之間的管理者,由它來管控著哪個模塊先載入,哪個後載入,維護著各模塊對外暴露的信息。

到這裡再來理解,有些文章中對 Module 對象的介紹:

  • module.id 模塊的識別符,通常是帶有絕對路徑的模塊文件名。
  • module.filename 模塊的文件名,帶有絕對路徑。
  • module.loaded 返回一個布爾值,表示模塊是否已經完成載入。
  • module.parent 返回一個對象,表示調用該模塊的模塊。
  • module.children 返回一個數組,表示該模塊要用到的其他模塊。
  • module.exports 表示模塊對外輸出的值。

這時,對於 Module 對象內的各屬性用途,理解應該會比較容易點了。

最後說一點,CommonJS 只是一種模塊化的規範,而 Node.js 才是這個規範的具體實現者,但 Node.js 通常用於服務端的運行環境,對於前端而言,對於瀏覽器而言,因為不存在 Node.js 這東西,所以 require 或 exports 這類在前端里是無法運行的,但可以藉助 Browerify 來進行代碼轉換。

AMD規範

AMD規範和規範實現者:Require.js

前端里沒有 Node.js 的存在,即使有類似的存在,但由於 CommonJS 的模塊化規範中,各模塊的載入行為是同步的,也就是被依賴的模塊必須執行結束,當前模塊才能繼續處理,這對於前端而言,模塊的載入就多了一個下載的過程,而網路是不可靠的,所以 CommonJS 並不適用於前端的場景。

所以,針對前端,提出了另一種模塊化規範:AMD,即非同步模塊載入,通過增加回調處理的方式,來移除被依賴模塊和當前模塊的前後關聯,兩個模塊可同時下載,執行。當前模塊內,需要依賴於其他模塊信息的代碼放置於回調函數內,這樣就可以先行處理當前模塊內其他代碼。

define

前端里是通過 <script> 來載入 JS 文件代碼的,不能像 Node.js 那樣從源頭上處理 JS 文件,所以它多了一個 define 來定義模塊,如:

//module.js
define(function(){
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    return {
        name: name,
        getWx: function() {
            return wx;
        }
    }
})

如果定義的模塊又依賴了其他模塊時,此時 define 需要接收兩個參數,如:

//兩個參數,第一個參數是數組,數組裡是當前模塊依賴的所有模塊,第二個參數是函數,函數需要參數,參數個數跟數組個數一直,也跟數組裡依賴的模塊一一對應,該模塊內部就是通過參數來訪問依賴的模塊。
define(['module2'], function(module2){
    //module2.xxx  使用模塊 module2 提供的介面
    
    //本模塊的內部邏輯
    
    return {
        //對外暴露的介面
    }
})

define 有兩個參數,第一個參數是數組,數組裡是當前模塊依賴的所有模塊,第二個參數是函數,函數需要參數,參數個數跟數組個數一直,也跟數組裡依賴的模塊一一對應,該模塊內部就是通過參數來訪問依賴的模塊。

require

如果其他地方有需要使用到某個模塊提供的功能時,此時就需要通過 require 來聲明依賴關係,但聲明依賴前,需要先通過 requirejs.config 來配置各個模塊的路徑信息,方便 Require.js 能夠獲得正確路徑自動去下載。

requirejs.config({
    paths: {
        module1: './module'
    }
})

var module1 = require(['module1'], function(module1){
    console.log(module1.name);    //訪問模塊內的 name 介面
    console.log(module1.getWx()); //訪問模塊內的 getWx 介面
});

//其他不依賴於模塊的代碼

這種方式的話,require 的第一個數組參數內的值就可以模塊的別稱,而第二個參數是個函數,同樣,函數的參數就是載入後的模塊,該 JS 文件內需要依賴到模塊信息的代碼都可以放到回調函數中,通過回調函數的參數來訪問依賴的模塊信息。

使用步驟

1.下載 Require.js,並放到項目中

Require.js:https://requirejs.org/docs/download.html#requirejs

2.新建作為模塊的文件,如 module.js,並通過 define 定義模塊

//module.js
define(function(){
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    return {
        name: name,
        getWx: function() {
            return wx;
        }
    }
})

3.在其他 js 文件內先配置所有的模塊來源信息

//main.js
requirejs.config({
    paths: {
        module1: './module'
    }
})

4.配置完模塊信息後,通過 require 聲明需要依賴的模塊

//main.js
var module1 = require(['module1'], function(module1){
    console.log(module1.name);    //訪問模塊內的 name 介面
    console.log(module1.getWx()); //訪問模塊內的 getWx 介面
});

//其他不依賴於模塊的代碼

5.最後也最重要的一步,在 html 中聲明 require.js 和 入口 js 如 main.js 的載入關係

<script src="js/lib/require.js" data-main="js/src/main.js"></script>

當然,這隻是基礎用法的步驟,其中第 3 步的模塊初始化步驟也可通過其他方式,如直接利用 require 的不同參數類型來實現等等,但大體上需要這幾個過程,尤其是最後一步,也是最重要一步,因為 AMD 在前端的具體實現都依靠於 Require.js,所以必須等到 Require.js 下載並執行結束後會開始處理其他 js。

以上例子的項目結構如圖:

小結

最後小結一下,AMD 規範的具體實現 Require.js 其實從使用上來看,已經比較容易明白它的原理是什麼了。

本質上,也還是利用了函數的特性,作為模塊存在的那些代碼本身已經通過 define 規範被定義在函數內了,所以模塊內的代碼自然對外是隱藏的,外部能訪問到的只有函數內 return 的介面,那麼這裡其實也就利用了閉包的特性。

所以,模塊化的實現,無非就是讓函數作為臨時命名空間結合閉包或者對象作為命名空間方式, 這種方式即使沒有 CommonJS 規範,沒有 AMD 規範,自己寫代碼很可以容易的實現。那麼為什麼會有這些規範技術的出現呢?

無非就是為了引入一個管理者的角色,沒有管理者的角色,模塊之間的依賴關係,哪個文件先載入,哪個後載入,<script> 的書寫順序都只能依靠人工來維護、管理。

而這些模塊化規範,其實目的就在於解決這些問題,CommonJS 是由 Node.js 作為管理者角色,來維護、控制各模塊的依賴關係,文件的載入順序。而 AMD 則是由 Require.js 來作為管理者角色,開發者不用在 HTML 里寫那麼多 <script>,而且也沒必要去關心這些文件誰寫前誰寫後,Require.js 會自動根據 require 來維護這些依賴關係,自動根據 requirejs.config 的配置信息去決定先載入哪個文件後載入哪個文件。

CMD規範

CMD 規範,類似於 AMD,同樣也是用於解決前端里的模塊化技術問題。而有了 AMD 為什麼還會有 CMD,我個人的理解是,AMD 的適用場景並沒有覆蓋整個前端里的需求,或者說 AMD 本身也有一些缺點,導致了新的 CMD 規範的出現。

比如說,從使用方式上,AMD 就有很多跟 CommonJS 規範不一致的地方,對於從 CommonJS 轉過來的這部分人來說,可能就會很不習慣;再比如說,AMD 考慮的場景可能太多,又要適配 jQurey,又要適配 Node 等等;

總之,CMD 規範總有它出現和存在的理由,下麵就大概來看看 CMD 的用法:

define&exports

類似於 AMD,CMD 規範中,也是通過 define 定義一個模塊:

//module.js
define(function (require, exports, module) {
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    exports.name = name;
    exports.getWx = () => wx;
})

跟 AMD 不一樣的地方是,CMD 中 define 只接收一個參數,參數類型是一個函數,函數的參數也很重要,有三個,按順序分別是 require,exports,module,作用就是 CommonJS 規範中三個命令的用途。

如果當前模塊需要依賴其他模塊,那麼在內部,使用 require 命令即可,所以,函數的三個參數很重要。

當前模塊如果不依賴其他模塊,也沒有對外提供任何介面,那麼,函數可以沒有參數,因為有了內部也沒有使用。

而如果當前模塊需要依賴其他模塊,那麼就需要使用到 require,所以函數第一個參數就是必須的;如果當前模塊需要對外暴露介面,那麼後兩個參數也是需要的;

總之,建議使用 define 定義模塊時,將函數三個參數都帶上,用不用再說,規範一點總沒錯。

require

在有需要使用某個模塊提供的功能時,通過 require 來聲明依賴關係:

//main.js
define(function (require, exports, module) {
    console.log("=====main.js=======");

    var module1 = require("./module");//同步載入模塊
    console.log(module1.name);

    require.async("./module2", function (module2) {//非同步載入模塊
        console.log(module2.wx);
    })
})

require 預設是以同步方式載入模塊,如果需要非同步載入,需要使用 require.async

使用步驟

1.下載 Sea.js,並放到項目中

Sea.js:https://github.com/seajs/seajs/releases

2.新建作為模塊的文件,如 module.js,並通過 define 定義模塊

//module.js
define(function (require, exports, module) {
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    exports.name = name;
    exports.getWx = () => wx;
})

3.其他需要依賴到該模塊的地方通過 require 聲明

//main.js
define(function (require, exports, module) {
    console.log("=====main.js=======");

    var module1 = require("./module");//同步載入模塊
    console.log(module1.name);

    require.async("./module2", function (module2) {//非同步載入模塊
        console.log(module2.wx);
    })
})

4.最後也最重要的一步,在 html 中先載入 sea.js 並指定主模塊的 js

<script src="js/lib/require.js"></script>
<script>
     seajs.use("./js/src/main.js");
</script>

使用步驟跟 AMD 很類似,首先是需要依賴於 Sea.js,所以必須先下載它。

然後定義模塊、依賴模塊、使用模塊的方式就跟 CommonJS 很類似,這幾個操作跟 AMD 會有些不同,也許這點也正是 CMD 存在的原因之一。

最後一步也是最重要一步,需要在 HTML 文檔中載入 sea.js 文檔,並指定入口的 js,註意做的事雖然跟 AMD 一樣,但實現方式不一樣。

小結

其實,CMD 跟 CommonJS 很類似,甚至在模塊化方面的工作,可以很通俗的將 sea.js 理解成 node.js 所做的事,只是有些 node.js 能完成但卻無法通過 sea.js 來負責的工作需要開發人員手動處理,比如定義一個模塊、通過 <script> 載入 sea.js 和指定主入口的 js 的工作。

CommonJS, AMD, CMD 三者間區別

下麵分別從適用場景、使用步驟、使用方式、特性等幾個方面來對比這些不同的規範:

適用場景

CommonJS 用於服務端,基於 Node.js 的運行環境;

AMD 和 CMD 用於前端,基於瀏覽器的運行環境;

使用方式

CommonJS 通過 require 來依賴其他模塊,通過 exports 來為當前模塊暴露介面;

AMD 通過 define 來定義模塊,通過 requirejs.config 來配置模塊路徑信息,通過 require 來依賴其他模塊,通過 retrurn 來暴露模塊介面;

CMD 通過 define 來定義模塊,通過 require 來依賴其他模塊,通過 exports 來為當前模塊暴露介面;

使用步驟

CommonJS 適用的 Node.js 運行環境,無需其他步驟,正常使用模塊技術即可;

AMD 適用的前端瀏覽器的運行環境沒有 Require.js,所以項目中需要先載入 Require.js,然後再執行主入口的 js 代碼,需要在 HTML 中使用類似如下命令:

<script src="js/lib/require.js" data-main="js/src/main.js"></script>

CMD 適用的前端瀏覽器的運行環境也沒有 Sea.js,所以項目中也需要先載入 Sea.js,然後再執行主入口的 js 代碼,需要在 HTML 中使用類似如下命令:

<script src="js/lib/sea.js"></script>
<script>
    seajs.use("./js/src/main.js");
</script>

特性

AMD:依賴前置、提前執行,如:

require(['module1','module2'], function(m1, m2){
    //...
})
define(['module1','module2'], function(m1, m2){
    //...
})

需要先將所有的依賴的載入完畢後,才會去處理回調中的代碼,這叫依賴前置、提前執行;

CMD:依賴就近、延遲執行,如:

define(function(require, exports, module){
    //...
    var module1 = require("./module1");
    //...
    require("./module2", function(m2){
        //...
    });
})

等代碼執行到 require 這行代碼時才去載入對應的模塊

ES6標準

ES6 中新增的模塊特性,在上一篇中已經稍微介紹了點,這裡也不具體展開介紹了,需要的話開頭的聲明部分有給出鏈接,自行查閱。

這裡就簡單說下,在前端瀏覽器中使用 ES6 模塊特性的步驟:

1.定義模塊,通過指定 <script type="module"> 方式

2.依賴其他模塊使用 import,模塊對外暴露介面時使用 export;

需要註意的一點是,當 JS 文件內出現 import 或者 export 時,這份 JS 文件必須聲明為模塊文件,即在 HTML 文檔中通過指定 <script> 標簽的 type 為 module,這樣 import 或 export 才能夠正常運行。

也就是說,使用其他模塊的功能時,當前的 JS 文件也必須是模塊。

另外,有一點,ES6 的模塊新特性,所有作為模塊的文件都需要開發人員手動去 HTML 文檔中聲明並載入,這是與其他方案不同的一點,ES6 中 import 只負責導入指定模塊的介面而已,聲明模塊和載入模塊都需要藉助 <script> 實現。

這裡不詳細講 ES6 的模塊特性,但想來講講,一些轉換工作的配置,因為:

  • 有些瀏覽器不支持 ES6 的語法,寫完 ES6 的代碼後,需要通過 Babel 將 ES6 轉化為 ES5。
  • 生成了 ES5 之後,裡面仍然有 require 語法,而瀏覽器並不認識 require 這個關鍵字。此時,可以用 Browserify 編譯打包 js,進行再次轉換。

而我是選擇使用 WebStrom 作為開發工具的,所以就來講講如何配置

WebStrom 的 Babel 配置

教程部分摘抄自:ES6的介紹和環境配置

1.新建項目
2.通過 npm 初始化項目

在安裝 Babel 之前,需要先用 npm 初始化我們的項目。打開終端或者通過 cmd 打開命令行工具,進入項目目錄,輸入如下命令: npm init -y,命令執行結束後,會在項目根目錄下生成 package.json 文件

3.(首次)全局安裝 Babel-cli

在終端里執行如下命令:

npm install -g babel-cli

4.本地安裝 babel-preset-es2015 和 babel-cli

npm install --save-dev babel-preset-es2015 babel-cli

本地是指在項目的根目錄下打開終端執行以上命令,執行結束,項目根目錄的 package.json 文件中會多了 devDependencies 選項

5.新建 .babelrc 文件

在根目錄下新建文件 .babelrc,輸入如下內容:

{
    "presets":[
        "es2015"
    ],
    "plugins":[]
}

6.執行命令轉換

babel js/src/main.js -o js/dist/main.js

-o 前是原文件,後面是轉換後的目標文件

這是我的項目結構:

.json 文件和 node_modules 文件夾都是操作完上述步驟後會自動生成的,最後執行完命令後,會在 dist 目錄下生成目標文件。

7.(可選)如果嫌每次執行的命令太過複雜,可利用 npm 腳本

babel js/src/main.js -o js/dist/main.js 這行代碼複製到 package.json 里的 scripts 欄位中:

以後每次都點一下 build 左邊的三角形按鈕運行一下腳本就可以了,省去了手動輸命令的時間。

8.(可選)如果還嫌每次手動點擊按鈕運行腳本麻煩,可配置監聽文件改動自動進行轉換

打開 WebStrom 的 Setting -> Tools -> File Watchers,然後點擊 + 按鈕,選擇 Babel 選項,然後進行配置:

9.最後,以後每次新的項目,除了第 3 步不用了之外,其餘步驟仍舊需要進行。

WebStrom 的 Browserify 配置

步驟跟上述很類似,區別僅在於一個下載 babel,這裡下載的是 browserify,以及轉換的命令不同而已:

1.新建項目
2.通過 npm 初始化項目

打開終端,進入到項目的根目錄,執行 npm init -y,執行結束後會在根目錄生成 package.json 文件

3.(首次)全局安裝 browserify

在終端里執行如下命令:

npm install browserify -g

4.執行命令轉換

browserify js/src/main.js -o js/dist/main.js --debug

-o 前是原文件,後面是轉換後的目標文件

5.(可選)如果嫌每次執行的命令太過複雜,可利用 npm 腳本

browserify js/src/main.js -o js/dist/main.js --debug 這行代碼複製到 package.json 里的 scripts 欄位中:

以後每次都點一下 build 左邊的三角形按鈕運行一下腳本就可以了,省去了手動輸命令的時間。

6.(可選)如果還嫌每次手動點擊按鈕運行腳本麻煩,可配置監聽文件改動自動進行轉換

打開 WebStrom 的 Setting -> Tools -> File Watchers,然後點擊 + 按鈕,選擇 <custom> 選項,然後進行配置:

7.最後,以後每次新的項目,除了第 3 步不用了之外,其餘步驟仍舊需要進行。


大家好,我是 dasu,歡迎關註我的公眾號(dasuAndroidTv),公眾號中有我的聯繫方式,歡迎有事沒事來嘮嗑一下,如果你覺得本篇內容有幫助到你,可以轉載但記得要關註,要標明原文哦,謝謝支持~
dasuAndroidTv2.png


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 客戶現場反饋,top的檢查結果中,一個CPU的占用一直是100%。實際上現場有4個CPU,而且這個伺服器是mysql專屬伺服器。 我的第一反應是io_thread一類的參數設置有問題,檢查以後發現read和write的thread設置都是4,這和CPU數一致,因此可以斷定這並不是單顆CPU占用過高的 ...
  • 臨時表概念 臨時表就是用來暫時保存臨時數據(亦或叫中間數據)的一個資料庫對象,它和普通表有些類似,然而又有很大區別。它只能存儲在臨時表空間,而非用戶的表空間。ORACLE臨時表是會話或事務級別的,只對當前會話或事務可見。每個會話只能查看和修改自己的數據。 臨時表語法 臨時表分類 ORACLE臨時表有 ...
  • 對於已經很熟悉T-SQL的讀者,或者對於較專業的DBA來說,邏輯的增刪改查,或者較複雜的SQL語句,都是非常簡單的,不存在任何挑戰,不值得一提,那麼,SQL的哪些方面是他們的挑戰 或者軟肋呢? 那就是sql優化。然而,要向成為一個好的Sql優化高手,首先要做的一件事無疑就是瞭解sql語句在SQL S ...
  • 創建不可重覆讀註意事項 語法:set transaction isolation level repeatable read。 指定語句不能讀取已由其他事務修改但尚未提交的行,並且指定,其他任何事務都不能在當前事務完成之前修改由當前事務讀取的數據。 對事務中的每個語句所讀取的全部數據都設置了共用鎖, ...
  • 剛開始學C#時候的筆記,只是些基礎的語句如有錯誤請批評指正,謝謝,(使用SqlServer2012以上) 一. 資料庫概述 SQI全稱 structrued Query Language 1、數據:能被電腦識別存儲處理的符號的集合。包括:數字、符號、圖片、聲音、視頻。 《英文全稱Data》 2、數 ...
  • 1.NSTimer 存在一定的誤差,不管是一次性的還是周期性的timer得實際觸發事件的時間,都會與所加入的runloop和runloopMode有關,如果此runloop正在執行一個連續性的運算,timer就會被延時觸發。 2.CADisplayLink CADisplayLink是一個能讓我們以 ...
  • dns,domain name system,功能變數名稱系統,把功能變數名稱轉化成ip的系統。 先來看幾上工具的使用,這幾個工具都能把功能變數名稱轉換成ip,都使用了dns。dns就好比資料庫,通過對它的查詢,能給url找到對應的ip。 ...
  • Execl表是經常要用到的存放二位數據的表格,Java也可以直接操作Execl表,經常用到的方式就是jxl和poi。 在這次項目中,我用到的poi往Execl中寫數據,剛開始設計的是前端發送一個ajax請求,後端響應後再瀏覽器下載Execl表。 以上代碼是最開始的設計思路,可是每次請求返回後,瀏覽器 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...