模塊化是指將一個複雜的程序分解爲多個模塊,方便編碼javascript
function m1(){
// xxx
}
function m2(){
// xxx
}
複製代碼
上面的函數m一、m2就至關於一個模塊,使用的時候,直接調用就能夠了。css
可是這種作法缺點也很明顯:因爲函數是直接掛載在window(全局)對象下,"污染"了全局變量,沒法保證不與其餘模塊發生變量名衝突,並且模塊成員之間看不出直接關係。html
既然window對象的可命名屬性名就那麼多,那我再在window(全局)對象上面聲明一個對象,而後把全部的模塊成員都放到這個對象裏面。前端
var module = {
count: 0,
function m1(){
// xxx
}
function m2(){
// xxx
}
}
複製代碼
上面的函數m1()和m2(),都封裝在module1對象裏。使用的時候,就是調用這個對象的屬性。vue
module.m1();
複製代碼
可是,這樣的寫法會暴露全部模塊成員,內部狀態能夠被外部改寫。好比,外部代碼能夠直接改變內部計數器的值。java
module1._count = 5;
複製代碼
爲了防止內部成員被暴露出去,咱們用當即執行函數能夠實現私有化變量。node
const module2 = (function() {
let _money = 100
const m1 = () => {
console.log(123)
}
const m2 = () => {
console.log(456)
}
return {
f1: m1,
f2: m2
}
})()
複製代碼
使用上面的寫法,外部代碼沒法讀取內部的_count變量。react
console.info(module2._count); //undefined
複製代碼
不過雖然這樣function內部的變量就對全局隱藏了,達到是封裝的目的。可是這樣仍是有缺陷的,Module2這個變量仍是暴漏到全局了,隨着模塊的增多,全局變量仍是會愈來愈多。jquery
<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>
<script type="text/javascript" src="c.js"></script>
<script type="text/javascript" src="d.js"></script>
複製代碼
缺點:git
(1)加載的時候會中止渲染網頁,引入的js文件越多,網頁失去響應的時間越長;
(2)會污染全局變量;
(3)js文件之間存在依賴關係,加載是有順序的,依賴性最大的要放到最後去加載;當項目規模較大時,依賴關係變得錯綜複雜。
(4)要引入的js文件太多,不美觀,代碼難以管理。
使用函數寫法會致使全局變量污染,並有可能致使命名衝突
使用命名空間會致使內部屬性被暴露,能夠致使內部成員被改寫
使用當即執行函數能夠實現私有化變量,能夠達到必定的防護做用。是早期較好的模塊化方案
使用Script來引用JS模塊會致使文件關係錯綜複雜,難以管理
2009年,美國程序員Ryan Dahl創造了node.js項目,將javascript語言用於服務器端編程。這標誌"Javascript模塊化編程"正式誕生。由於老實說,在瀏覽器環境下,沒有模塊也不是特別大的問題,畢竟網頁程序的複雜性有限;可是在服務器端,必定要有模塊,與操做系統和其餘應用程序互動,不然根本無法編程。而node.js的模塊系統,就是參照CommonJS規範實現的。
3.1.一、CommonJS特色
3.1.二、基本語法
module.exports = value
或exports.xxx = value
require(xxx)
,若是是第三方模塊,xxx爲模塊名;若是是自定義模塊,xxx爲模塊文件路徑。此處咱們有個疑問:CommomJS暴露的模塊究竟是什麼?CommonJS規範規定,每一個模塊內部,module變量表明當前模塊,這個變量是一個對象,它的exports屬性(即module.exports)是對外的接口。加載某個模塊,實際上是加載該模塊的module.exports屬性。
// a.js
module.exports = {
a: 1
}
// or
exports.a = 1
// b.js
var module = require('./a.js')
module.a // -> log 1
複製代碼
上面的寫法很好用,可是 module.exports 和 exports 是咋回事?爲啥這幾句代碼就實現模塊化了,讓咱們來看一下基礎的實現
先說 require 吧
var module = require('./a.js')
module.a
// 這裏其實就是包裝了一層當即執行函數,這樣就不會污染全局變量了,
// 重要的是 module 這裏,module 是 Node 獨有的一個變量
module.exports = {
a: 1
}
// 基本實現
var module = {
id: 'xxxx', // 我總得知道怎麼去找到他吧
exports: {} // exports 就是個空對象
}
// 這個是爲何 exports 和 module.exports 用法類似的緣由
var exports = module.exports
var load = function (module) {
// 導出的東西
var a = 1
module.exports = a
return module.exports
};
// 而後當我 require 的時候去找到獨特的
// id,而後將要使用的東西用當即執行函數包裝下,over
複製代碼
再來講說module.exports
與exports
的區別。
exports
是指向的module.exports
的引用module.exports
初始值爲一個空對象{},因此exports
初始值也是{},可是不能對exports
直接賦值,不會有任何效果,,看了上面代碼的同窗確定明白爲何了。require()
返回的是module.exports
而不是exports
3.1.三、模塊的加載機制
CommonJS模塊的加載機制是,輸入的是被輸出的值的拷貝。也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。 這點與ES6模塊化有重大差別(下文會介紹),請看下面這個例子:
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
複製代碼
上面代碼輸出內部變量counter和改寫這個變量的內部方法incCounter。
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;
console.log(counter); // 3
incCounter();
console.log(counter); // 3
複製代碼
上面代碼說明,counter輸出之後,lib.js模塊內部的變化就影響不到counter了。這是由於counter是一個原始類型的值,會被緩存。除非寫成一個函數,才能獲得內部變更後的值。
CommonJS規範是 Node 獨有的,若是瀏覽器想使用該規範,就須要用到 Browserify 解析了。
3.1.四、Browserify
Browserify 可讓你使用相似於 node 的 require() 的方式來組織瀏覽器端的 Javascript 代碼,經過 預編譯 讓前端 Javascript 能夠直接使用 Node NPM 安裝的一些庫。 -- 來自百度百科
①下載
②打包編譯 將須要打包編譯的JS文件經過 運行代碼browserify app.js -o bundle.js
將路徑下的app.js文件編譯output到bundle.js文件中
③頁面使用引入 最後從新在頁面文件中引入bundle.js
文件<script type="text/javascript" src="./bundle.js"></script>
3.1.五、總結
見名知意,就是異步模塊定義。上面已經介紹過,CommonJS是服務器端模塊的規範,主要是爲了JS在後端的表現制定的,不太適合前端。而AMD就是要爲前端JS的表現制定規範。因爲不是JavaScript原生支持,使用AMD規範進行頁面開發須要用到對應的庫函數,也就是require.js(還有個js庫:curl.js)。實際上AMD 是 require.js在推廣過程當中對模塊定義的規範化的產出。 AMD採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。
3.2.一、模塊的定義和使用
// 定義一個模塊
define('module', ['dep'], function (dep) {
return exports;
});
// id 可選參數,用來定義模塊的標識,若是沒有提供該參數,默認腳本文件名(去掉拓展名)
// dependencies 是一個當前模塊用來的模塊名稱數組,(所依賴模塊的數組)
// factory 工廠方法,模塊初始化要執行的函數或對象,若是爲函數,它應該只被執行一次,若是是對象,此對象應該爲模塊的輸出值。
複製代碼
require.js也採用require()語句加載模塊,可是不一樣於CommonJS,它要求二個參數
//導入和使用模塊
require([module], callback);
// 第二個參數[module],是一個數組,裏面的成員就是要加載的模塊;
// 第二個參數callback,則是加載成功以後的回調函數
// 等到前面的module加載完成以後,這個回調函數才被調用。
// 加載的模塊會以參數形式傳入該函數,從而在回調函數內部就可使用這些模塊
複製代碼
3.2.二、看個例子
// demo.html
<body>
//引入所依賴文件和主入口文件
<script src="./require.js" data-main = './demo.js'></script>
</body>
// modules/m1.js
define(function(){
var name = 'm1-amd';
function getName(){
return name;
}
return {getName} //暴露出的模塊
})
// modules/m2.js
//在m2模塊中,引用了m1模塊
define(['m1'],function(m1){
var msg = 'm2-amd';
function show(){
console.log(msg,m1.getName());
}
return {show} //暴露的模塊
})
//demo.js
(function(){
//配置每一個變量對應的模塊路徑
require.config({
paths: {
m1: './modules/m1',
m2: './modules/m2',
}
})
require(['m2'],function(m2){
m2.show(); //結果:m2-amd m1-amd
})
})()
複製代碼
默認狀況下,require.js假定這加載的模塊與main.js在同一個目錄,而後自動加載。若是不在同一目錄,咱們可使用require.config()方法對模塊的加載行爲進行自定義
在上面的例子中也能夠引用第三方庫,只需在上面代碼的基礎稍做修改:
//demo.js
(function(){
//配置每一個變量對應的模塊路徑
require.config({
paths: {
m1: './modules/m1',
m2: './modules/m2',
jquery:'./jquery-3.3.1'
}
})
require(['m2','jquery'],function(m2,$){
m2.show(); //結果:m2-amd m1-amd
$('body').css('backgroundColor','#000');
})
})()
複製代碼
不過須要注意的是:jquery對模塊化作了各類不一樣的規範,對每一個不一樣模塊都有暴露出的接口名字,對AMD暴露出的接口名字是小寫jquery,所以不能把jquery寫成大寫的jQuery,這樣會報錯
//jquery 3.3.1.js
if(typeof define === 'function' && define.amd){
define('jquery',[],function(){
return jQuery;
})
}
複製代碼
3.2.三、AMD特色:
依賴前置:必須等到全部依賴的模塊加載完成以後纔會執行回調,即便在回調里根本沒用到該模塊。(在定義模塊的時候就要聲明其依賴的模塊),不過目前在AMD2.0也能夠動態加載模塊了
requireJS優缺點
優勢:
一、適合在瀏覽器環境中異步加載模塊
二、能夠並行加載多個模塊
缺點:
一、提升了開發成本,代碼的閱讀和書寫比較困難,模塊定義方式的語義不暢
二、不符合通用的模塊化思惟方式,是一種妥協的實現
即通用模塊定義,對應SeaJS,是阿里玉伯團隊首先提出的概念和設計。跟requireJS解決一樣問題,只是運行機制不一樣。
3.3.一、CMD與AMD的不一樣的在於
(1)AMD推崇依賴前置;CMD推崇依賴就近,只有在用到某個模塊的時候再去require:
通俗來講:
AMD
在加載完成定義(define
)好的模塊就會當即執行,全部執行完成後,遇到require
纔會執行主邏輯。(提早加載)
CMD
在加載完成定義(define
)好的模塊,僅僅是下載不執行,在遇到require
纔會執行對應的模塊。(按需加載)
AMD
用戶體驗好,由於沒有延遲,CMD
性能好,由於只有用戶須要的時候才執行。
CMD
爲何會出現,由於對node.js的書寫者友好,由於符合寫法習慣,就像爲什麼vue會受人歡迎的一個道理。
3.3.二、CMD語法
Sea.js 推崇一個模塊一個文件,遵循統一的寫法。
define(id?, deps?, factory)
由於CMD推崇一個文件一個模塊,因此常常就用文件名做爲模塊id CMD推崇依賴就近,因此通常不在define的參數中寫依賴,在factory中寫 factory有三個參數
function(require, exports, module)
定義暴露模塊
//定義沒有依賴的模塊
define(function(require, exports, module){
exports.xxx = value
module.exports = value
})
複製代碼
//定義有依賴的模塊
define(function(require, exports, module){
//引入依賴模塊(同步)
var module2 = require('./module2')
//引入依賴模塊(異步)
require.async('./module3', function (m3) {
})
//暴露模塊
exports.xxx = value
})
複製代碼
引入使用模塊
define(function (require) {
var m1 = require('./module1')
var m4 = require('./module4')
m1.show()
m4.show()
})
複製代碼
sea.js 簡單使用教程
①下載sea.js, 並引入
官網: seajs.org/
github : github.com/seajs/seajs
而後將sea.js導入項目: js/libs/sea.js
②建立項目結構
|-modules
|-m1.js
|-m2.js
|-m3.js
|-m4.js
|-index.html
|-main.js
|-sea.js
複製代碼
③定義sea.js的模塊代碼
// index.html
<body>
<script src="./sea.js"></script> //引入依賴文件
<script>
seajs.use('./main.js'); //設置主入口文件
</script>
</body>
// modules/m1.js
define(function(require,exports,module){
var msg = 'm1';
function foo(){
console.log(msg);
}
module.exports = { //暴露的接口
foo:foo
}
});
// modules/m2.js
define(function(require,exports,module){
var msg = 'm2';
function bar(){
console.log(msg);
}
module.exports = bar; /暴露的接口
});
// modules/m3.js
define(function(require,exports,module){
var msg = 'm3';
function foo(){
console.log(msg);
}
exports.m3 = { foo:foo} /暴露的接口
});
// modules/m4.js
define(function(require,exports,module){
var msg = 'm4';
// 同步引入
var m2 = require('./m2');
m2();
// 異步引入
require.async('./m3',function(m3){
m3.m3.foo();
});
function fun(){
console.log(msg);
}
exports.m4 = fun; /暴露的接口
})
//main.js
define(function(require,exports,module){
var m1 = require('./modules/m1');
m1.foo();
var m4 = require('./modules/m4');
m4.m4();
})
複製代碼
最後獲得結果以下
3.3.三、CMD優缺點優勢: 一樣實現了瀏覽器端的模塊化加載。 能夠按需加載,依賴就近。
缺點: 依賴SPM打包,模塊的加載邏輯偏重
ES6 模塊的設計思想是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。CommonJS 和 AMD 模塊,都只能在運行時肯定這些東西。好比,CommonJS 模塊就是對象,輸入時必須查找對象屬性。
3.4.1 語法
在 ES6 中,使用export關鍵字來導出模塊,使用import關鍵字引用模塊。可是瀏覽器尚未徹底兼容,須要使用babel轉換成ES5的require。
// 導出
export function hello() { };
export default {
// ...
};
// 導入
import { readFile } from 'fs';
import React from 'react';
複製代碼
使用import導入模塊時,須要知道要加載的變量名或函數名。
在ES6中還提供了export default,爲模塊指定默認輸出.對應導入模塊import時,不須要使用大括號。
//math.js
var num = 0;
var add = function (a, b) {
return a + b;
};
export { num, add };
//導入
import { num, add } from './math';
function test(ele) {
ele.textContent = add(1 + num);
}
複製代碼
3.4.二、ES6與CommonJS的區別
CommonJS
對於基本數據類型,屬於複製。即會被模塊緩存。同時,在另外一個模塊能夠對該模塊輸出的變量從新賦值。
對於複雜數據類型,屬於淺拷貝。因爲兩個模塊引用的對象指向同一個內存空間,所以對該模塊的值作修改時會影響另外一個模塊。
當使用require命令加載某個模塊時,就會運行整個模塊的代碼。
當使用require命令加載同一個模塊時,不會再執行該模塊,而是取到緩存之中的值。也就是說,CommonJS模塊不管加載多少次,都只會在第一次加載時運行一次,之後再加載,就返回第一次運行的結果,除非手動清除系統緩存。
循環加載時,屬於加載時執行。即腳本代碼在require的時候,就會所有執行。一旦出現某個模塊被"循環加載",就只輸出已經執行的部分,還未執行的部分不會輸出。
ES6模塊
ES6模塊中的值屬於【動態只讀引用】。
對於只讀來講,即不容許修改引入變量的值,import的變量是隻讀的,不管是基本數據類型仍是複雜數據類型。當模塊遇到import命令時,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裏面去取值。
對於動態來講,原始值發生變化,import加載的值也會發生變化。不管是基本數據類型仍是複雜數據類型。
循環加載時,ES6模塊是動態引用。只要兩個模塊之間存在某個引用,代碼就可以執行。
3.4.三、優缺點
優勢:
一、容易進行靜態分析
二、面向將來的 EcmaScript 標準
缺點:
一、瀏覽器尚未徹底兼容,必須經過工具轉換成標準的 ES5 後才能正常運行。
二、全新的命令字,新版的 Node.js才支持
CommonJS規範主要用於服務端編程,加載模塊是同步的,這並不適合在瀏覽器環境,由於同步意味着阻塞加載,瀏覽器資源是異步加載的,所以有了AMD CMD解決方案。
AMD規範在瀏覽器環境中異步加載模塊,並且能夠並行加載多個模塊。不過,AMD規範開發成本高,代碼的閱讀和書寫比較困難,模塊定義方式的語義不暢。
CMD規範與AMD規範很類似,都用於瀏覽器編程,依賴就近,延遲執行,能夠很容易在Node.js中運行。不過,依賴SPM 打包,模塊的加載邏輯偏重
ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。