前端工程師必備:前端的模組化

木頭房子發表於2019-04-12

模組化的理解

  • 什麼是模組?
    • 將一個複雜的程式依據一定的規則(規範)封裝成幾個塊(檔案), 並進行組合在一起;
    • 塊的內部資料/實現是私有的, 只是向外部暴露一些介面(方法)與外部其它模組通訊;
  • 一個模組的組成
    • 資料--->內部的屬性;
    • 運算元據的行為--->內部的函式;
  • 模組化是指解決一個複雜的問題時自頂向下把系統劃分成若干模組的過程,有多種屬性,分別反映其內部特性;
  • 模組化編碼:編碼時是按照模組一個一個編碼的, 整個專案就是一個模組化的專案;

非模組化的問題

  • 頁面載入多個js的問題:
<script type="text/javascript" src="module1.js"></script>
<script type="text/javascript" src="module2.js"></script>
<script type="text/javascript" src="module3.js"></script>
<script type="text/javascript" src="module4.js"></script>
複製程式碼
  • 發生問題:
    • 難以維護 ;
    • 依賴模糊;
    • 請求過多;
  • 所以,這些問題可以通過現代模組化編碼和專案構建來解決;

模組化的優點

  • 更好地分離:避免一個頁面中放置多個script標籤,而只需載入一個需要的整體模組即可,這樣對於HTML和JavaScript分離很有好處;
  • 更好的程式碼組織方式:有利於後期更好的維護程式碼;
  • 按需載入:提高使用效能,和下載速度,按需求載入需要的模組
  • 避免命名衝突:JavaScript本身是沒有名稱空間,經常會有命名衝突,模組化就能使模組內的任何形式的命名都不會再和其他模組有衝突。
  • 更好的依賴處理:使用模組化,只需要在模組內部申明好依賴的就行,增加刪除都直接修改模組即可,在呼叫的時候也不用管該模組依賴了哪些其他模組。

模組化的發展歷程

原始寫法

  • 只是把不同的函式簡單地放在一起,就算一個模組;
function fun1(){
&emsp;&emsp;//...
}
function fun2(){
&emsp;&emsp;//...
}
//上面的函式fun1,fun2組成了一個模組,使用的時候直接呼叫某個函式就行了。
複製程式碼
  • 缺點:
    • "汙染"了全域性變數,無法保證不與其他模組發生變數名衝突;
    • 模組成員之間看不出直接關係。

物件寫法

  • 為了解決汙染全域性變數的問題,可以把模組寫成一個物件,所有的模組成員都放到這個物件裡面。
&emsp;var module1 = new Object({
	count : 0,
	fun1 : function (){
&emsp;&emsp;&emsp;&emsp;&emsp;//...
&emsp;&emsp;  },
&emsp;&emsp;fun2 : function (){
&emsp;&emsp;&emsp;&emsp;&emsp;//...
&emsp;&emsp;&emsp;}
&emsp;});
&emsp;//這個裡面的fun1和fun2都封裝在一個賭俠寧裡,可以通過物件.方法的形式進行呼叫;
&emsp;module1.fun1();
複製程式碼
  • 優點:
    • 減少了全域性上的變數數目;
  • 缺點:
    • 本質是物件,而這個物件會暴露所有模組成員,內部狀態可以被外部改寫。

立即執行函式(IIFE模式)

  • 避免暴露私有成員,所以使用立即執行函式(自調函式,IIFE);
  • 作用: 資料是私有的, 外部只能通過暴露的方法操作
var module1 = (function(){
	var count = 0;
	var fun1 = function(){
		//...
	}
	var fun2 = function(){
		//...
	}
	//將想要暴露的內容放置到一個物件中,通過return返回到全域性作用域。
	return{
		fun1:fun1,
		fun2:fun2
	}
})()
//這樣的話只能在全域性作用域中讀到fun1和fun2,但是讀不到變數count,也修改不了了。
//問題:當前這個模組依賴另一個模組怎麼辦?
複製程式碼

IIFE的增強(引入依賴)

  • 如果一個模組很大,必須分成幾個部分,或者一個模組需要繼承另一個模組,這時就有必要採用"增強模式";
  • IIFE模式增強:引入依賴;
  • 這就是現代模組實現的基石;
var module1 = (function (mod){
&emsp;&emsp;mod.fun3 = function () {
&emsp;&emsp;&emsp;//...
	};
	return mod;
})(module1);
//為module1模組新增了一個新方法fun3(),然後返回新的module1模組。

//引入jquery到專案中;
var Module = (function($){
    var _$body = $("body");     // we can use jQuery now!
    var foo = function(){
    console.log(_$body);    // 特權方法
    }
  // Revelation Pattern
	  return {
	      foo: foo
	  }
})(jQuery)
Module.foo();
複製程式碼

js模組化需要解決那些問題:

  • 1.如何安全的包裝一個模組的程式碼?(不汙染模組外的任何程式碼)
  • 2.如何唯一標識一個模組?
  • 3.如何優雅的把模組的API暴漏出去?(不能增加全域性變數)
  • 4.如何方便的使用所依賴的模組?

模組化規範

  • Node: 伺服器端
  • Browserify : 瀏覽器端

CommonJS:伺服器端

  • 概述

    • Node 應用由模組組成,採用 CommonJS 模組規範。
    • CommonJS規範規定,每個模組內部,module變數代表當前模組。這個變數是一個物件,它的exports屬性(即module.exports)是對外的介面。載入某個模組,其實是載入該模組的module.exports屬性。
  • 特點

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

    • 定義暴露模組 : exports
    exports.xxx = value
    // 通過module.exports指定暴露的物件value
    module.exports = value
    複製程式碼
    • 引入模組 : require
    var module = require('模組相對路徑')
    複製程式碼
  • 引入模組發生在什麼時候?

    • Node:執行時, 動態同步引入;
    • Browserify:在執行前對模組進行編譯/轉譯/打包的處理(已經將依賴的模組包含進來了), 執行的是打包生成的js, 執行時不需要再從遠端引入依賴模組;

CommonJS通用的模組規範(同步)

  • Node內部提供一個Module構建函式。所有模組都是Module的例項。
  • 每個模組內部,都有一個module物件,代表當前模組。
  • module.exports屬性表示當前模組對外輸出的介面,其他檔案載入該模組,實際上就是讀取module.exports變數。
  • Node為每個模組提供一個exports變數,指向module.exports。
  • 如果一個模組的對外介面,就是一個單一的值,不能使用exports輸出,只能使用module.exports輸出。
  • Modules/1.0規範包含內容:
    1. 模組的標識應遵循的規則(書寫規範)
    2. 定義全域性函式require,通過傳入模組標識來引入其他模組,執行的結果即為模組暴露出來的API;
    3. 如果被require函式引入的模組中也包含依賴,那麼依次載入這些依賴;
    4. 如果引入模組失敗,那麼require函式應該報一個異常;
    5. 模組通過變數exports來向外暴露API,exports賦值暴露的只能是一個物件exports = {Obj},暴露的API須作為此物件的屬性。exports本質是引入了module.exports的物件。不能直接將exports變數指向一個值,因為這樣等於切斷了exports與module.exports的聯絡。
    6. 如果暴露的不是變數exports,而是module.exports。module變數代表當前模組,這個變數是一個物件,它的exports屬性(即module.exports)是對外的介面。載入某個模組,其實是載入該模組的module.exports屬性。exports=module.exports={Obj}
node中的commonJS教程
  • 1.安裝node.js;
  • 2.建立專案結構
//結構如下
|-modules
	|-module1.js//待引入模組1
	|-module2.js//待引入模組2
	|-module3.js//待引入模組3
|-app.js//主模組
|-package.json
	{
	  "name": "commonjsnode",
	  "version": "1.0.0"
	}
複製程式碼
  • 3.下載第三方模組:舉例express

    npm i express --save
    複製程式碼
  • 4.模組化編碼

// module1 
// 使用module.exports = value向外暴露一個物件
module.exports = {
	name: 'this is module1',
    foo(){
        console.log('module1 foo()');
    }
}
// module2 
// 使用module.exports = value向外暴露一個函式 
module.exports = function () {
    console.log('module2()');
}
// module3 
// 使用exports.xxx = value向外暴露一個物件
 exports.foo = function () {
     console.log('module3 foo()');
 };
 exports.bar = function () {
     console.log('module3 bar()');
 };
 exports.name = 'this is module3'

//app.js檔案
var uniq = require('uniq');
//引用模組
let module1 = require('./modules/module1');
let module2 = require('./modules/module2');
let module3 = require('./modules/module3');
//使用模組
module1.foo();
module2();
module3.foo();
module3.bar();
module3.name;
複製程式碼
  • 5.通過node執行app.js
    • 命令:node.app.js
    • 工具:右鍵-->執行
瀏覽器中的commonJS教程
  • 藉助Browserify
  • 步驟
    1. 建立專案結構
    |-js
    	 |-dist //打包生成檔案的目錄
    	 |-src //原始碼所在的目錄
    		   |-module1.js
    		   |-module2.js
    		   |-module3.js
    		   |-app.js //應用主原始檔
    |-index.html //瀏覽器上的頁面
    |-package.json
    	{
    	   "name": "browserify-test",
    	   "version": "1.0.0"
    	}
    複製程式碼
    1. 下載browserify

      • 全域性: npm install browserify -g
      • 區域性: npm install browserify --save-dev
    2. 定義模組程式碼:index.html檔案要執行在瀏覽器上,需要藉助browserify將app.js檔案打包編譯,如果直接在index.html引入app.js就會報錯。

    3. 打包處理js:根目錄下執行browserify js/src/app.js -o js/dist/bundle.js

    4. 頁面使用引入:

    <script type="text/javascript" src="js/dist/bundle.js"></script> 
    複製程式碼

AMD : 瀏覽器端

  • CommonJS規範載入模組是同步的,也就是說,只有載入完成,才能執行後面的操作。
  • AMD規範則是非同步載入模組,允許指定回撥函式,可以實現非同步載入依賴模組,並且會提前載入;
  • 由於Node.js主要用於伺服器程式設計,模組檔案一般都已經存在於本地硬碟,所以載入起來比較快,不用考慮非同步載入的方式,所以CommonJS規範比較適用。
  • 如果是瀏覽器環境,要從伺服器端載入模組,這時就必須採用非同步模式,因此瀏覽器端一般採用AMD規範。

語法

AMD規範基本語法
  • 定義暴露模組: define([依賴模組名], function(){return 模組物件})
  • 引入模組: require(['模組1', '模組2', '模組3'], function(m1, m2){//使用模組物件})
相容CommonJS規範的輸出模組
define(function (require, exports, module) { 
    var reqModule = require("./someModule");
    requModule.test();
    exports.asplode = function () {
        //someing
    }
}); 
複製程式碼

AMD:非同步模組定義規範(預載入)

  • AMD規範:github.com/amdjs/amdjs…

  • AMD是"Asynchronous Module Definition"的縮寫,意思就是"非同步模組定義"。

  • 它採用非同步方式載入模組,模組的載入不影響它後面語句的執行。所有依賴這個模組的語句,都定義在一個回撥函式中,等到載入完成之後,這個回撥函式才會執行。

  • AMD也採用require()語句載入模組,但是不同於CommonJS,它要求兩個引數:

    require([module], callback);
    複製程式碼
    • 第一個引數[module],是一個陣列,裡面的成員就是要載入的模組;
    • 第二個引數callback,則是載入成功之後的回撥函式。
  • 目前,主要有兩個Javascript庫實現了AMD規範:RequireJS和curl.js。

RequireJS

  • 優點
    • 實現js檔案的非同步載入,避免網頁失去響應;
    • 管理模組之間的依賴性,便於程式碼的編寫和維護。
require.js使用教程
  1. 下載require.js, 並引入
  1. 建立專案結構
|-js
  |-libs
    |-require.js // 引入的require.js
  |-modules
    |-alerter.js
    |-dataService.js
  |-main.js
|-index.html
複製程式碼
  1. 定義require.js的模組程式碼

    • require.js載入的模組,採用AMD規範。也就是說,模組必須按照AMD的規定來寫。
    • 具體來說,就是模組必須採用特定的define()函式來定義;
      • 如果一個模組不依賴其他模組,那麼可以直接定義在define()函式之中。
    define(['myLib'], function(myLib){
    &emsp; function foo(){
    &emsp;&emsp;&emsp;&emsp;myLib.doSomething();
    &emsp;&emsp;}
    &emsp;&emsp;// 暴露模組
    &emsp;&emsp;return {foo : foo};
    });
    //當require()函式載入上面這個模組的時候,就會先載入myLib.js檔案。
    複製程式碼
     - 如果這個模組還依賴其他模組,那麼define()函式的第一個引數,必須是一個陣列,指明該模組的依賴性;
    
     ```
     // dataService.js
     define(function () {
       let msg = 'this is dataService'
       function getMsg() {
     	return msg.toUpperCase()
       }
       return {getMsg}
     })
     
     // alerter.js
     define(['dataService', 'jquery'], function (dataService, $) {
       let name = 'Tom2'
       function showMsg() {
         $('body').css('background', 'gray')
         alert(dataService.getMsg() + ', ' + name)
       }
       return {showMsg}
     })
     ```
    複製程式碼
  2. 應用主(入口)js: main.js

    • 使用require.config()方法,我們可以對模組的載入行為進行自定義。require.config()就寫在主模組main.js的頭部,引數就是一個物件,這個物件的paths屬性指定各個模組的載入路徑。
     (function () {
        //配置
        require.config({
          //基本路徑
          baseUrl: "js/",
          //模組標識名與模組路徑對映
          paths: {
            "alerter": "modules/alerter",//此處不能寫成alerter.js,會報錯
            "dataService": "modules/dataService",
          }
        })
        
        //引入使用模組
        require( ['alerter'], function(alerter) {
          alerter.showMsg()
        })
      })()
    複製程式碼
  3. 頁面使用模組:

<script data-main="js/main" src="js/libs/require.js"></script>
複製程式碼
定義模組
  • require.config()接受一個配置物件,這個物件除了有前面說過的paths屬性之外,還有一個shim屬性,專門用來配置不相容的模組。
  • 具體來說,每個模組要定義:
    • 1、exports值(輸出的變數名),表明這個模組外部呼叫時的名稱;
    • 2、deps陣列,表明該模組的依賴性。
  • 支援的配置項:
    • baseUrl :所有模組的查詢根路徑。
      • 當載入純.js檔案(依賴字串以/開頭,或者以.js結尾,或者含有協議),不會使用baseUrl。
      • 如未顯式設定baseUrl,則預設值是載入require.js的HTML所處的位置。如果用了data-main屬性,則該路徑就變成baseUrl。
      • baseUrl可跟require.js頁面處於不同的域下,RequireJS指令碼的載入是跨域的。唯一的限制是使用text! plugins載入文字內容時,這些路徑應跟頁面同域,至少在開發時應這樣。優化工具會將text! plugin資源內聯,因此在使用優化工具之後你可以使用跨域引用text! plugin資源的那些資源。
    • paths:path對映那些不直接放置於baseUrl下的模組名。
      • 設定path時起始位置是相對於baseUrl的,除非該path設定以"/"開頭或含有URL協議(如http:)。
      • 用於模組名的path不應含有.js字尾,因為一個path有可能對映到一個目錄。路徑解析機制會自動在對映模組名到path時新增上.js字尾。在文字模版之類的場景中使用require.toUrl()時它也會新增合適的字尾。
      • 在瀏覽器中執行時,可指定路徑的備選(fallbacks),以實現諸如首先指定了從CDN中載入,一旦CDN載入失敗則從本地位置中載入這類的機制;
    • shim: 為那些沒有使用define()來宣告依賴關係、設定模組的"瀏覽器全域性變數注入"型指令碼做依賴和匯出配置。
使用第三方基於require.js的框架(jquery)
  • 將jquery的庫檔案匯入到專案: js/libs/jquery-1.10.1.js
  • 在main.js中配置jquery路徑
paths: {
   'jquery': 'libs/jquery-1.10.1'
}
複製程式碼
  • 在alerter.js中使用jquery
define(['dataService', 'jquery'], function (dataService, \$) {
     var name = 'xfzhang'
     function showMsg() {
         $('body').css({background : 'red'})
         alert(name + ' '+dataService.getMsg())
     }
     return {showMsg}
 })
複製程式碼
使用第三方不基於require.js的框架(angular)
  • 將angular.js匯入專案:js/libs/angular.js
    • 流行的函式庫(比如jQuery)符合AMD規範,更多的庫並不符合。這樣的模組在用require()載入之前,要先用require.config()方法,定義它們的一些特徵。
// main.js中配置
(function () {
 //配置
 require.config({
   //基本路徑
   baseUrl: "js/",
   //模組標識名與模組路徑對映
   paths: {
     //第三方庫作為模組
     'jquery' : './libs/jquery-1.10.1',
     'angular' : './libs/angular',
     //自定義模組
     "alerter": "./modules/alerter",
     "dataService": "./modules/dataService"
   },
   /*
    配置不相容AMD的模組
    exports : 指定與相對應的模組名對應的模組物件
    */
   shim: {
     'angular' : {
       exports : 'angular'
     }
   }
 })
 //引入使用模組
 require( ['alerter', 'angular'], function(alerter, angular) {
     alerter.showMsg()
     console.log(angular);
 })
})()
複製程式碼

CMD : 瀏覽器端

  • CMD規範:github.com/seajs/seajs…
  • CMD規範專門用於瀏覽器端,模組的載入是非同步的,模組使用時才會載入執行。
  • CMD規範整合了CommonJS和AMD規範的特點。
  • 在 Sea.js 中,所有 JavaScript 模組都遵循 CMD模組定義規範
  • 基本語法
    • 定義暴露模組:
      // 沒有依賴的模組
      define(function(require, module, exports){
         let value = 'xxx';
         //通過require引入依賴模組
         //通過module.exports/exports來暴露模組
         exports.xxx = value
         module.exports = value
      })
      // 有依賴的模組
      define(function(require, exports, module){
         //引入依賴模組(同步)
         var module2 = require('./module2')
         //引入依賴模組(非同步)
         require.async('./module3', function (m3) {
       	  ......
         })
         //暴露模組
         exports.xxx = value
      })
      複製程式碼
    • 使用模組seajs.use(['模組1', '模組2'])

sea.js簡單使用教程

  1. 下載sea.js, 並引入
    define()
    exports
    module.exports
    複製程式碼
    • 如何依賴模組:require()
    • 如何使用模組: seajs.use()
  2. 建立專案結構
|-js
  |-libs
    |-sea.js
  |-modules
    |-module1.js
    |-module2.js
    |-module3.js
    |-module4.js
    |-main.js
|-index.html
複製程式碼
  1. 定義sea.js的模組程式碼

    • module1.js
    define(function (require, exports, module) {
      //內部變數資料
      var data = 'this is module1'
      //內部函式
      function show() {
        console.log('module1 show() ' + data)
      }
      //向外暴露
      exports.show = show
    })
    複製程式碼
    • module2.js
    define(function (require, exports, module) {
      module.exports = {
        msg: 'I Will Back'
      }
    })
    複製程式碼
    • module3.js
    define(function (require, exports, module) {
     const API_KEY = 'abc123'
     exports.API_KEY = API_KEY
    })
    複製程式碼
    • module4.js
    define(function (require, exports, module) {
     //引入依賴模組(同步)
     var module2 = require('./module2');
     function show() {
       console.log('module4 show() ' + module2.msg)
     }
     exports.show = show
     //引入依賴模組(非同步)
     require.async('./module3', function (m3) {
       console.log('非同步引入依賴模組3  ' + m3.API_KEY)
     })
    })
    複製程式碼
    • main.js : 主(入口)模組
    define(function (require) {
      var m1 = require('./module1')
      var m4 = require('./module4')
      m1.show()
      m4.show()
    })
    複製程式碼
  2. index.html:

<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
  seajs.use('./js/modules/main')
</script>
複製程式碼

ES6模組化

  • 模組化的規範:CommonJS和AMD兩種。前者用於伺服器,後者用於瀏覽器。
  • 而ES6 中提供了簡單的模組系統,完全可以取代現有的CommonJS和AMD規範,成為瀏覽器和伺服器通用的模組解決方案。
  • ES6 模組的設計思想,是儘量的靜態化,使得編譯時就能確定模組的依賴關係,以及輸入和輸出的變數。CommonJS 和 AMD 模組,都只能在執行時確定這些東西。

基本用法

  • es6 中新增了兩個命令 export 和 import ;

    • export 命令用於規定模組的對外介面;
    • import 命令用於輸入其他模組提供的功能。
    • 一個模組就是一個獨立的檔案。該檔案內部的所有變數,外部無法獲取。
    • 如果你希望外部能夠讀取模組內部的某個變數,就必須使用export關鍵字輸出該變數。
    • 下面是一個JS檔案,裡面使用export命令輸出變數。
    // math.js
    export const add = function (a, b) {
        return a + b
    }
    export const subtract = function (a, b) {
        return a - b
    }
    複製程式碼
    • 使用export命令定義了模組的對外介面以後,其他JS檔案就可以通過import命令載入這個模組(檔案)。
    // main.js
    import { add, subtract } from './math.js'
    add(1, 2)
    substract(3, 2)
    複製程式碼
  • 定義暴露模組 : export

    • 暴露一個物件:
      • 預設暴露,暴露任意資料型別,暴露什麼資料型別,接收什麼資料型別
      export default 物件
      複製程式碼
    • 暴露多個:
      • 常規暴露,暴露的本質是物件,接收的時候只能以物件的解構賦值的方式來接收值
      export var xxx = value1
      export let yyy = value2
      // 暴露一個物件
      var xxx = value1
      let yyy = value2
      export {xxx, yyy}
      複製程式碼
  • 引入使用模組 : import

    • default模組:
    import xxx  from '模組路徑/模組名'
    複製程式碼
    • 其它模組
    import {xxx, yyy} from '模組路徑/模組名'
    import * as module1 from '模組路徑/模組名'
    複製程式碼

export 詳細用法

  • export不止可以匯出函式,還可以匯出,物件、類、字串等等;

  • 暴露多個:

    1. 分別暴露
    export const obj = {test1: ''}
    export const test = ''
    export class Test {
       constuctor() {
       }
    }
    // 或者,直接在暴露的地方定義匯出函式或者變數
    export let foo = ()=>{console.log('fnFoo');return "foo"},bar="stringBar"
    複製程式碼
    1. 一起暴露,推薦使用這種寫法,這樣可以寫在指令碼尾部,一眼就看清楚輸出了哪些變數。
    let a=1
    let b=2
    let c=3
    export { a,b,c }
    複製程式碼
    1. 還可以通過as改變輸出名稱
    // test.js
    let a = 1
    let b = 2
    let c = 3
    export {
        a as test,
        b,
        c
    };
    import { test, b, c } from './test.js' // 改變命名後只能寫 as 後的命名
    複製程式碼
    1. 通過萬用字元暴露其他引入的模組
    // test.js
    let a = 1
    let b = 2
    let c = 3
    export {
        a as test,
        b,
        c
    };
    // lib.js引入test.js的內容
    export * from './test.js'
    // 引入
    import {test,b,c} from './lib.js'
    複製程式碼
  • 暴露一個物件,預設暴露

    • export default指定預設輸出,import無需知道變數名就可以直接使用
    // test.js
    export default function () {
        console.log('hello world')
    }
    //引入
    import say from './test.js' // 這裡可以指定任意變數名
    say() // hello world
    複製程式碼
    • 常用的模組
    import $ from 'jQuery'   // 載入jQuery 庫
    import _ from 'lodash'   // 載入 lodash
    import moment from 'moment' // 載入 moment
    複製程式碼

import詳細用法

  • import 為載入模組的命令,基礎使用方式和之前一樣
// main.js
import { add, subtract } from './test'

// 對於export default 匯出的
import say from './test'
複製程式碼
  • 通過 as 命令修改匯入的變數名
import {add as sum, subtract} from './test'
sum (1, 2)
複製程式碼
  • 載入模組的全部,除了指定輸出變數名或者 export.default 定義的匯入, 還可以通過 * 號載入模組的全部。
// math.js
export const add = function (a, b) {
    return a + b
}
export const subtract = function (a, b) {
    return a - b
}

//引入
import * as math from './test.js'
math.add(1, 2)
math.subtract(1, 2)
複製程式碼

ES6-Babel-Browserify使用教程

  • 問題: 所有瀏覽器還不能直接識別ES6模組化的語法
  • 解決:
    • 使用Babel將ES6--->ES5(使用了CommonJS) ----瀏覽器還不能直接執行;
    • 使用Browserify--->打包處理js----瀏覽器可以執行
  1. 定義package.json檔案
{
  "name" : "es6-babel-browserify",
  "version" : "1.0.0"
}
複製程式碼
  1. 安裝babel-cli, babel-preset-es2015和browserify
npm install babel-cli browserify -g
npm install babel-preset-es2015 --save-dev 
複製程式碼
  1. 定義.babelrc檔案,這是一個babel的設定檔案
{
   "presets": ["es2015"]
 }
複製程式碼
  1. 編碼
// js/src/module1.js
export function foo() {
	 console.log('module1 foo()');
};
export let bar = function () {
	 console.log('module1 bar()');
};
export const DATA_ARR = [1, 3, 5, 1];

// js/src/module2.js
let data = 'module2 data'; 
function fun1() {
	console.log('module2 fun1() ' + data);
};
function fun2() {
	console.log('module2 fun2() ' + data);
};
export {fun1, fun2};

// js/src/module3.js
export default {
  name: 'Tom',
  setName: function (name) {
    this.name = name
  }
}

// js/src/app.js
import {foo, bar} from './module1'
import {DATA_ARR} from './module1'
import {fun1, fun2} from './module2'
import person from './module3'
import $ from 'jquery'
//引入完畢
$('body').css('background', 'red')
foo()
bar()
console.log(DATA_ARR);
fun1()
fun2()
person.setName('JACK')
console.log(person.name);
複製程式碼
  1. 編譯
  • 使用Babel將ES6編譯為ES5程式碼(但包含CommonJS語法) : babel js/src -d js/lib
  • 使用Browserify編譯js : browserify js/lib/app.js -o js/lib/bundle.js
  1. 頁面中引入測試
<script type="text/javascript" src="js/lib/bundle.js"></script>
複製程式碼
  1. 引入第三方模組(jQuery)

    • 1). 下載jQuery模組:
    npm install jquery@1 --save
    複製程式碼
    • 2). 在app.js中引入並使用
    import $ from 'jquery'
    $('body').css('background', 'red')
    複製程式碼

總結

模組化方案 優點 缺點
commonJS 複用性強;
使用簡單;
實現簡單;
有不少可以拿來即用的模組,生態不錯;
同步載入不適合瀏覽器,瀏覽器的請求都是非同步載入;
不能並行載入多個模組。
AMD 非同步載入適合瀏覽器 可並行載入多個模組;
模組定義方式不優雅,不符合標準模組化
ES6 可靜態分析,提前編譯 面向未來的標準;
瀏覽器原生相容性差,所以一般都編譯成ES5;
目前可以拿來即用的模組少,生態差

AMD和CMD區別:

  • 權威參考:github.com/seajs/seajs…
  • 對於依賴的模組,AMD 是提前執行,CMD 是延遲執行。
    • 不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible.
  • CMD 推崇依賴就近,AMD 推崇依賴前置。
    // CMD
    define(function(require, exports, module) {   
    	var a = require('./a');
    	a.doSomething()   // 此處略去 100 行   
    	var b = require('./b') // 依賴可以就近書寫   
    	b.doSomething()   // ... })
    // AMD 預設推薦的是
    define(['./a', './b'], function(a, b) {  
    	// 依賴必須一開始就寫好    
    	a.doSomething()    // 此處略去 100 行    
    	b.doSomething()   
    	 ...})
    複製程式碼
    • 雖然 AMD 也支援 CMD 的寫法,同時還支援將 require 作為依賴項傳遞,但 RequireJS 的作者預設是最喜歡上面的寫法,也是官方文件裡預設的模組定義寫法。
  • AMD 的 API 預設是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。比如 AMD 裡,require 分全域性 require 和區域性 require,都叫 require。CMD 裡,沒有全域性 require,而是根據模組系統的完備性,提供 seajs.use 來實現模組系統的載入啟動。CMD 裡,每個 API 都簡單純粹。
  • 還有一些細節差異,具體看這個規範的定義就好,就不多說了。

參考:使用 AMD、CommonJS 及 ES Harmony 編寫模組化的 JavaScript

相關文章