模塊化是組織代碼的一種方式。將全部的js
業務邏輯代碼寫在一個文件裏面,不只致使文件龐大,並且難以管理和維護。javascript
好比:html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js</title>
</head>
<body>
<script> ...// 一些業務邏輯 </script>
</body>
</html>
複製代碼
爲了方便維護,能夠經過外部引入的方式:前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js</title>
</head>
<body>
...
<script type="javascript" src="...(file path)"></script>
</body>
</html>
複製代碼
這種方式我的看來也是一種模塊化
的方式,只不過這種方式存在許多弊端。vue
// test.js是基於jQuery.js開發的,也就是說test.js是依賴於jQuery.js的,因此jQuery必須先於test.js引入。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js</title>
</head>
<body>
...
<script type="javascript" src="jQuery.js"></script>
<script type="javascript" src="test.js"></script>
</body>
</html>
複製代碼
// a.js
var a=1;
// b.js
var a=2;
//test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="a.js"></script>
<script src="b.js"></script>
<title>js</title>
</head>
<body>
<script> alert('a的值爲'+a); </script>
</body>
</html>
複製代碼
執行結果以下:java
爲了解決這些問題,一些模塊化的規範就出現了。jquery
先想想,爲何模塊很重要?git
由於有了模塊,咱們就能夠更方便地使用別人的代碼,想要什麼功能,就加載什麼模塊。github
可是,這樣作有一個前提,那就是你們必須以一樣的方式編寫模塊,不然你有你的寫法,我有個人寫法,豈不是亂了套!考慮到 Javascript
模塊如今尚未官方規範,這一點就更重要了。面試
js
不像其餘高級語言有模塊系統,標準庫較少和更缺少包管理系統express
js
起初只有全局對象的形式,經過一個個小函數來實現不一樣的模塊功能
function m1(){
  //...
}
function m2(){
  //...
}
複製代碼
漸漸發展,經過構建對象的形式,來武裝不一樣的功能
var module1 = new Object ({
  _count : 0,
  m1 : function (){
    //...
  },
  m2 : function (){
    //...
  }
});
複製代碼
上面的函數 m1()
和 m2()
,都封裝在 module1
對象裏。使用的時候,就是調用這個對象的屬性:
module1.m1();
可是,這樣的寫法會暴露全部模塊成員,內部狀態能夠被外部改寫。好比,外部代碼能夠直接改變內部計數器的值。
module1._count = 5;
複製代碼
繼續發展,經過當即執行函數和閉包的形式來分離一個又一個的小組件
3、當即執行函數寫法
使用"當即執行函數"(Immediately-Invoked Function Expression,IIFE),能夠達到不暴露私有成員的目的。
var module1 = (function(){
  var _count = 0;
  var m1 = function(){
    //...
  };
  var m2 = function(){
    //...
  };
  return {
  m1 : m1,
  m2 : m2
  };
})();
複製代碼
使用上面的寫法,外部代碼沒法讀取內部的_count 變量。
console.info (module1._count); //undefined
複製代碼
module1
就是Javascript
模塊的基本寫法。
下面,再對這種寫法進行加工。
放大模式( augmentation)
若是一個模塊很大,必須分紅幾個部分,或者一個模塊須要繼承另外一個模塊,這時就有必要採用"放大模式"(augmentation)。
var module1 = (function (mod){
  mod.m3 = function () {
    //...
  };
  return mod;
})(module1);
複製代碼
上面的代碼爲 module1
模塊添加了一個新方法 m3()
,而後返回新的 module1
模塊。
寬放大模式(Loose augmentation)
在瀏覽器環境中,模塊的各個部分一般都是從網上獲取的,有時沒法知道哪一個部分會先加載。若是採用上一節的寫法,第一個執行的部分有可能加載一個不存在的空對象,這時就要採用"寬放大模式"。
var module1 = ( function (mod){
  //...
  return mod;
})(window.module1);
複製代碼
與"放大模式"相比,"寬放大模式"就是"當即執行函數"的參數能夠是空對象。
當對象多起來的時候,又開始經過命名空間,來實現分級管理
輸入全局變量
獨立性是模塊的重要特色,模塊內部最好不與程序的其餘部分直接交互。
爲了在模塊內部調用全局變量,必須顯式地將其餘變量輸入模塊。
var module1 = (function ($, YAHOO) {
  //...
})(jQuery, YAHOO);
複製代碼
上面的 module1
模塊須要使用 jQuery
庫和 YUI
庫,就把這兩個庫(實際上是兩個模塊)看成參數輸入 module1
。這樣作除了保證模塊的獨立性,還使得模塊之間的依賴關係變得明顯
commonJS
規範的提出成了javascript
歷史上最重要的里程碑。主流的模塊化的規範有:
commonJs
規範AMD
規範(Asynchronous Module Definition) (異步模塊定義)CMD
規範ES6 module
CommonJS
規範CommonJS
規範的使用Node.js
是commonJS
規範的主要實踐者,它有四個重要的環境變量爲模塊化的實現提供支持:module
、exports
、require
、global
。實際使用時,用module.exports
定義當前模塊對外輸出的接口(不推薦直接用exports
),用require
加載模塊。
// 定義模塊math.js
var basicNum = 0;
function add(a, b) {
return a + b;
}
module.exports = { //在這裏寫上須要向外暴露的函數、變量
add: add,
basicNum: basicNum
}
// 引用自定義的模塊時,參數包含路徑,可省略.js
var math = require('./math');
math.add(2, 5);
// 引用核心模塊時,不須要帶路徑
var http = require('http');
http.createService(...).listen(3000);
複製代碼
commonJS
用**同步
**的方式加載模塊。在服務端,模塊文件都存在本地磁盤,讀取很是快,因此這樣作不會有問題。可是在瀏覽器端,限於網絡緣由,更合理的方案是使用異步加載。
commomJS
的實現原理commonJS
簡化版源碼
function Module(id, parent){
this.id = id;
this.exports = {};
this.parent = parent;
this.filename = null;
this.loaded = false;
this.children = []
}
Module.prototype.require = function(path){
return Module._load(path, this)
}
//由此可知,require 並非全局命令,而是每一個模塊提供的一個內部方法,也就是說,只有在模塊內部才能使用require命令,(惟一的例外是REPL 環境)。另外,require 其實內部調用 Module._load 方法。
Module._load = function(request, parent, isMain) {
// 計算絕對路徑
var filename = Module._resolveFilename(request, parent);
// 第一步:若是有緩存,取出緩存
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
}
// 第二步:是否爲內置模塊
if (NativeModule.exists(filename)) {
return NativeModule.require(filename);
}
// 第三步:生成模塊實例,存入緩存
var module = new Module(filename, parent);
Module._cache[filename] = module;
// 第四步:加載模塊
try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}
// 第五步:輸出模塊的exports屬性
return module.exports;
};
module.exports = Module;
複製代碼
Module
類的一個實例。module
是global
全局對象的一個屬性。global
對象也有一個全局函數require()
,global
對象的require()
是對module.require()
函數的進一步抽象和封裝。AMD
規範AMD是"Asynchronous Module Definition"的縮寫,意思就是"異步模塊定義"。它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。
AMD
規範有了服務器端模塊之後,很天然地,你們就想要客戶端模塊。並且最好二者可以兼容,一個模塊不用修改,在服務器和瀏覽器均可以運行。
可是,因爲一個重大的侷限,使得CommonJS
規範不適用於瀏覽器環境。仍是上一節的代碼,若是在瀏覽器中運行,會有一個很大的問題。
var math = require('math');
math.add(2, 3);
複製代碼
第二行math.add(2, 3)
,在第一行require('math')
以後運行,所以必須等math.js
加載完成。也就是說,若是加載時間很長,整個應用就會停在那裏等。
這對服務器端不是一個問題,由於全部的模塊都存放在本地硬盤,能夠同步加載完成,等待時間就是硬盤的讀取時間。可是,對於瀏覽器,這倒是一個大問題,由於模塊都放在服務器端,等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於"假死"狀態。
所以,瀏覽器端的模塊,不能採用"同步加載"(synchronous
),只能採用"異步加載"(asynchronous
)。這就是AMD
規範誕生的背景。
AMD
規範的使用AMD
也採用require()
語句加載模塊,可是不一樣於CommonJS
,它要求兩個參數:
require([module], callback);
複製代碼
第一個參數[module]
,是一個數組,裏面的成員就是要加載的模塊;第二個參數callback
,則是加載成功以後的回調函數。若是將前面的代碼改寫成AMD
形式,就是下面這樣:
require(['math'], function (math) {
math.add(2, 3);
});
複製代碼
math.add()
與math
模塊加載不是同步的,瀏覽器不會發生假死。因此很顯然,AMD
比較適合瀏覽器環境。
目前,主要有兩個Javascript
庫實現了AMD
規範:require.js和curl.js。
AMD
規範的寫法require.js 加載的模塊,採用 AMD
規範。也就是說,模塊必須按照 AMD
的規定來寫。 具體來講,就是模塊必須採用特定的 define()
函數來定義。若是一個模塊不依賴其餘模塊。那麼能夠直接定義在 define()
函數之中。 假定如今有一個 math.js
文件,它定義了一個math
模塊。那麼,math.js
就要這樣寫:
// math.js
define(function (){
 var add = function (x,y){
  return x+y;
 };
 return {
  add: add
 };
});
複製代碼
加載方法以下:
// main.js
require(['math'], function (math){
 alert(math.add(1,1));
});
複製代碼
若是這個模塊還依賴其餘模塊,那麼define()
函數的第一個參數,必須是一個數組,指明該模塊的依賴性。
define(['myLib'], function(myLib){
 function foo(){
  myLib.doSomething();
 }
 return {
  foo : foo
 };
});
複製代碼
當 require()
函數加載上面這個模塊的時候,就會先加載myLib.js
文件。
加載非規範的模塊
理論上,require.js 加載的模塊,必須是按照 AMD 規範、用 define()
函數定義的模塊。可是實際上,雖然已經有一部分流行的函數庫(好比jQuery
)符合 AMD
規範,更多的庫並不符合。那麼,require.js
是否可以加載非規範的模塊呢? 回答是能夠的。 這樣的模塊在用 require()
加載以前,要先用 require.config()
方法,定義它們的一些特徵。 舉例來講,underscore
和 backbone
這兩個庫,都沒有采用 AMD
規範編寫。若是要加載它們的話,必須先定義它們的特徵。
require.config({
 shim: {
  'underscore': {
   exports: '_'
  },
  'backbone': {
   deps: ['underscore', 'jquery'],
   exports: 'Backbone'
  }
 }
});
複製代碼
require.config()
接受一個配置對象,這個對象除了有前面說過的paths
屬性以外,還有一個 shim
屬性,專門用來配置不兼容的模塊。具體來講,每一個模塊要定義: (1)exports
值(輸出的變量名),代表這個模塊外部調用時的名稱; (2)deps
數組,代表該模塊的依賴性。 好比,jQuery
的插件能夠這樣定義:
shim: { 
'jquery.scroll': {  
deps: ['jquery'], 
exports: 'jQuery.fn.scroll'
}
}
複製代碼
CMD
規範CMD
是Common Module definition
的縮寫,即通用模塊定義。CMD
規範是國內發展出來的,就像AMD
有個requireJS
,CMD
有個瀏覽器的實現SeaJS
,SeaJS
要解決的問題和requireJS
同樣,只不過在模塊定義方式和模塊加載(能夠說運行、解析)時機上有所不一樣
CMD
規範的寫法define
,用來定義模塊。在CMD
中,一個模塊就是一個文件,格式爲:
define( factory );
複製代碼
JSON
數據模塊:define({ "foo": "bar" });
複製代碼
2.經過字符串定義模板模塊:
define('this is `data`.');
複製代碼
factory 爲函數的時候,表示模塊的構造方法,執行構造方法即可以獲得模塊向外提供的接口。
require 是一個方法,接受模塊標識做爲惟一參數,用來獲取其餘模塊提供的接口:require(id)
define(function( require, exports ){
var a = require('./a');
a.doSomething();
});
複製代碼
require
是同步往下執行的,須要的異步加載模塊可使用 require.async
來進行加載:
define( function(require, exports, module) {
require.async('.a', function(a){
a.doSomething();
});
});
複製代碼
require.resolve( id )
可使用模塊內部的路徑機制來返回模塊路徑,不會加載模塊。
exports 是一個對象,用來向外提供模塊接口
module 是一個對象,上面存儲了與當前模塊相關聯的一些屬性和方法
define( function(require, exports, module) {
// 模塊代碼
});
複製代碼
示例:
// math.js
define(function(require,exports,module) {
exports.add = function() {
var sum = 0,i = 0,args = arguments,l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
});
複製代碼
// increment.js
define(function(require,exports,module) {
var add = require('math').add;
exports.increment = function(val) {
return add(val,1);
};
});
複製代碼
// program.js
define(function(require ,exports,module) {
var inc = require('increment').increment;
var a = 1;
inc(a); // 2
module.id == "program";
});
複製代碼
AMD
規範和CMD
規範的比較AMD
是提早執行,CMD
是延遲執行。CMD
推崇 as lazy as possibleCMD
推崇依賴就近,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
(requirejs
)模塊會提早執行**,用戶體驗好,**而CMD
(seajs
)性能好,由於只有在須要時候才執行。ES6
模塊規範ES6
的語言規格中引入了模塊化功能,也就很好的取代了以前的commonjs
和AMD
規範,成爲了瀏覽器和服務器的通用的模塊解決方案,在現今(vuejs
,ReactJS
)等框架大行其道中,都引入了ES6
中的模塊化(Module
)機制。
Es6
中模塊導出的基本語法模塊的導出,export
關鍵字用於暴露數據,暴露給其餘模塊
使用方式是,能夠將export
放在任何變量,函數或類聲明的前面,從而將他們從模塊導出,而import
用於引入數據,例如以下所示:
將下面這些js
存儲到exportExample.js
中,分別導出的是數據,函數,類:
exportExample.js
// 1. 導出數據,變量前面加上export關鍵字
export var name = "隨筆川跡"; // 導出暴露name變量
export let weChatPublic = "itclanCoder"; // 暴露weChatPublic
export const time = 2018; // 暴露time
// 2. 導出函數,函數前面加上export關鍵字
export function sum(num1,num2){
return num1+num2;
}
/* * * 以上等價於 * function sum(num1,num2){ * return num1+num2; * } * export sum; * 也能夠這樣:在定義它時沒有立刻導出它,因爲沒必要老是導出聲明,能夠導出引用,所以下面這段代碼也是能夠運行的 */
// 3. 導出類,類前面加上export關鍵字
export class People{
constructor(name,age){
this.name = name;
this.age = age;
}
info(){
return `${this.name}${this.age}歲了`;
}
}
複製代碼
注意:一個模塊就是一個獨立的文件,該文件內部的全部變量,外部沒法獲取,一樣,任何未顯示導出的變量,函數或類都是模塊私有的,若沒有用export
對外暴露,是沒法從模塊外部訪問的 例如:
function countResult(num1,num2){
return num1-num2;
}
// 沒有經過export關鍵字導出,在外部是沒法訪問該模塊的變量或者函數的
複製代碼
對應在另外一個模塊中經過import
導入以下所示,模塊命名爲importExample.js
import { name, weChatPublic,time,sum,People} from "../modelTest1/exportExampleEs5.js"
var people = new People("小美",18); // 實例化perople對象
console.log(name);
console.log(weChatPublic);
console.log(time);
console.log(sum(1,2));
console.log(people.info());
複製代碼
注意:在上面的示例中,除了export
關鍵字外,每個聲明與腳本中的如出一轍,由於導出的函數和類聲明須要有一個名稱,因此代碼中的每個函數或類也確實有這個名稱,除非用default
關鍵字,不然不能用這個語法導出匿名函數或類。
Es6
中模塊導入的基本語法若是想從一個文件(模塊)訪問另外一個文件(模塊)的功能,則須要經過import
關鍵字在另外一個模塊中引入數據,import語句的兩個部分組成分別是**:要導入的標識符和標識符應當從那個模塊導入,**另外,導入的標識符的順序能夠是任意位置,可是導入的標識符(也就是大括號裏面的變量)與export暴露出的變量名應該是一致的。具體的寫法以下:
import {identifer1,indentifer2} from "./example.js" // import {標識符1,標識符2} from "本地
複製代碼
1. 導入單個綁定
// 只導入一個
import {sum} from "./example.js"
console.log(sum(1,2)); // 3
sum = 1; // 拋出一個錯誤,是不能對導入的綁定變量對象進行改寫操做的
複製代碼
2. 導入多個綁定
若是想從示例模塊中導入多個綁定,與單個綁定類似,多個綁定值之間用逗號隔開便可:
// 導入多個
import {sum,multiply,time} from "./exportExample.js"
console.log(sum(1,2)); // 3
console.log(multiply(1,2)); // 3
console.log(time); // 2018
複製代碼
在這段代碼中,從exportExample.js
模塊導入3
個綁定,sum
,multiply
和time
以後使用它們,就像使用本地定義的同樣 等價於下面這個: **無論在import語句中把一個模塊寫了多少次,該模塊將只執行一次,導入模塊的代碼執行後,實例化過的模塊被保存在內存中,**只要另外一個import
語句使用它就能夠重複使用它.
import {sum} from "./exportExample.js"
import {multiply} from "./exportExample.js"
import {time} from "./exportExample.js
複製代碼
3. Es6
中導入整個模塊
特殊狀況下,能夠導入整個模塊做爲一個單一的對象,而後全部的導出均可以做爲對象的屬性使用,例如:
// 導入一整個模塊
import * as example from "./exportExample.js"
console.log(example.sum(1,example.time));
consoole.log(example.multiply(1,2));// multiply與sum函數功能同樣
複製代碼
在上面這段代碼中,從本地模塊的exportExample.js
中導出的全部綁定被加載到一個被稱做爲example
的對象中,指定的導出sum()
函數,multiply()
函數和time
以後做爲example
的屬性被訪問,這種導入格式被稱爲命名空間導入,由於exportExample.js
文件中不存在example
對象,因此它被做爲exportExample.js
中全部導出成員的命名空間對象而被建立
Es6
中如何給導入導出時標識符重命名從一個模塊導入變量,函數或者類時,咱們可能不但願使用他們的原始名稱,就是導入導出時模塊內的標識符(變量名,函數,或者類)能夠不用一一對應,保持一致**,**能夠在導出和導入過程當中改變導出變量對象的名稱
使用方式①: 使用as
關鍵字來指定變量,函數,或者類在模塊外應該被稱爲何名稱,例如以下一函數:
function sum(num1,num2){
return num1+num2;
}
export {sum as add} // as後面是從新指定的函數名
複製代碼
如上代碼,函數sum
是本地名稱,add
是導出時使用的名稱,換句話說,當另外一個模塊要導入這個函數時,必須使用add這個名稱:
若在importExample.js
一模塊中,則導入的變量對象應是add
而不是sum
,是由它導出時變量對象決定的
import {add} from "./exportExample.js"
複製代碼
使用方式②: 使用as
關鍵字來指定變量,函數,或者類在主模塊內應該被稱爲何名稱,例如以下一函數:
// exportExample.js
export function sum(num1,num2){
return num1+num2;
}
複製代碼
// importExample.js
import {sum as add} from "./exportExample.js"
console.log(sum(1,2)); // 3
複製代碼
如上代碼導入add
函數時使用了一個導入名稱來重命名sum
函數,注意這種寫法與前面導出export
時的區別,使用import
方式時,從新命名的標識符在前面,as
後面是本地名稱,可是這種方式,即便導入時改變函數的本地名稱,即便模塊導入了add
函數,在當前模塊中也沒有add()
標識符,如上對add
的類型檢測就是很好的驗證.
ES6
匿名方式的導入和導出若是在不給導出的標識符(變量,函數,類)呢,那麼能夠經過導出default
關鍵字指定單個變量,函數或者類, 在import
的時候, 名字隨便寫, 由於每個模塊的默認接口就一個。
//a.js
let sex = "boy";
export default sex //(sex不能加大括號)
//本來直接export sex外部是沒法識別的,加上default就能夠了.可是一個文件內最多隻能有一個export default。 其實此處至關於爲sex變量值"boy"起了一個系統默認的變量名default,天然default只能有一個值,因此一個文件內不能有多個export default。
複製代碼
// b.js
//本質上,a.js文件的export default輸出一個叫作default的變量,而後系統容許你爲它取任意名字。因此能夠爲import的模塊起任何變量名,且不須要用大括號包含
import any from "./a.js"
import any12 from "./a.js"
console.log(any,any12) // boy,boy
複製代碼