js模組化程序
模組化程序
模組就是實現特定功能的一組方法。
1、無模組時代:通過script標籤來匯入一個個的js檔案,而一個js檔案中,程式碼簡單的堆在一起,把不同的函式簡單地放在一起,就算是一個模組,只要能從上往下依次執行就可以了。帶來的問題是:全域性變數命名衝突、模組成員看不出關係、依賴關係不好管理。
2、模組萌芽時代:
- 把模組寫成一個物件,所有的模組成員都放到這個物件裡面。
var module1 = new Object({
_count : 0,
m1 : function (){
//...
},
m2 : function (){
//...
}
});
module1.m1();
這樣的寫法會暴露所有模組成員,內部狀態可以被外部改寫。
- 用自執行函式來包裝程式碼
modA = function(){
var a,b; //變數a、b外部不可見
return {
add : function(c){
a + b + c;
},
format: function(){
//......
}
}
}()
這樣function內部的變數就對全域性隱藏了,達到是封裝的目的。但是這樣還是有缺陷的,modA這個變數還是暴漏到全域性了,隨著模組的增多,全域性變數還是會越來越多。
- java風格的名稱空間
為了避免全域性變數造成的衝突,人們想到或許可以用多級名稱空間來進行管理。用程式設計師的名字作為字首:
xiaoming.num=1;
xiaohong.num=2;
- jQuery風格的匿名自執行函式
//jquery的封裝
$(function(window){
//通過給window新增屬性而暴漏到全域性
window.jQuery = window.$ = jQuery;
})(window)
jQuery的封裝風格曾經被很多框架模仿,通過匿名函式包裝程式碼,所依賴的外部變數傳給這個函式,在函式內部可以使用這些依賴,然後在函式的最後把模組自身暴漏給window。
這種風格雖然靈活了些,但並未解決根本問題:所需依賴還是得外部提前提供、還是增加了全域性變數。
模組化要解決的問題
- 封裝,模組內的程式碼不能汙染其他模組。
- 標識,唯一的標識一個模組。
- 匯出,將模組內的API暴露出去。
- 引入,引入其他模組的API。
3、CommonJS–伺服器端的模組化方案
node.js的產生,將javascript語言用於伺服器端程式設計。
在瀏覽器環境下,沒有模組也不是特別大的問題,畢竟網頁程式的複雜性有限;但是在伺服器端,一定要有模組,與作業系統和其他應用程式互動,否則根本沒法程式設計。
//math.js
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
//increment.js
var add = require('math').add;
exports.increment = function(val) {
return add(val, 1);
};
//program.js
var inc = require('increment').increment;
var a = 1;
inc(a);
- 這使得一個js檔案就是一個模組,檔名作為模組標識。
- 模組通過變數exports來向外暴漏API,exports只能是一個物件,暴漏的API須作為此物件的屬性。
- 定義全域性函式require,通過傳入模組標識來引入其他模組,執行的結果即為別的模組暴漏出來的API。
- 如果被require函式引入的模組中也包含依賴,那麼依次載入這些依賴。
4、CommonJS不能用於瀏覽器端。
- 在瀏覽器端,外層沒有function包裹,變數全暴漏在全域性。
- 瀏覽器端資源的載入方式與服務端完全不同。服務端require一個模組,直接就從硬碟或者記憶體中讀取,消耗的時間可以忽略。而瀏覽器需要從服務端下載這個檔案,然後執行裡面的程式碼才能得到API,需要花費一個http請求。
- 瀏覽器端需要function包裝,需要非同步載入。
5、前端模組化方案
(1)保守派:開發時用CommonJS寫法來寫程式碼,執行時用工具將程式碼轉換為適合瀏覽器使用的程式碼。如browserify、webpack
(2)革新派:模組在定義的時候指明所依賴的模組,然後把本模組的程式碼寫在回撥函式裡。模組的載入通過下載-回撥這樣的過程來進行。形成了AMD這種瀏覽器端的js模組化規範。
(3)中間派:採納了CommonJS和AMD的部分優點。CommonJS的通過require來宣告依賴,AMD的模組預先載入以及通過return可以暴漏任意型別的資料,而不是像commonjs那樣exports只能為object。最終他們制定了一個Modules/Wrappings規範。
- 全域性有一個module變數,用來定義模組。
- 通過module.declare方法來定義一個模組。
- module.declare方法只接收一個引數,那就是模組的factory,此factory可以是函式也可以是物件,如果是物件,那麼模組輸出就是此物件。
- 模組的factory函式傳入三個引數:require,exports,module,用來引入其他依賴和匯出本模組API。
- 如果factory函式最後明確寫有return資料(js函式中不寫return預設返回undefined),那麼return的內容即為模組的輸出。
//可以使用exprots來對外暴漏API
module.declare(function(require, exports, module)
{
exports.foo = "bar";
});
//也可以直接return來對外暴漏資料
module.declare(function(require)
{
return { foo: "bar" };
});
AMD和require.js
非同步載入所需的模組,然後在回撥函式中執行主邏輯。
1. 用全域性函式define來定義模組。用法為:define(dependencies, factory);
2. 檔名為模組標識,遵從CommonJS Module Identifiers規範
3. dependencies為依賴的模組陣列,在factory中需傳入形參與之一一對應。
4. 如果dependencies的值中有”require”、”exports”或”module”,則與commonjs中的實現保持一致。
5. 如果dependencies省略不寫,則預設為[“require”, “exports”, “module”],factory中也會預設傳入require,exports,module。
6. 如果factory為函式,模組對外暴漏API的方法有三種:return任意型別的資料、exports.xxx=xxx、module.exports=xxx。
7. 如果factory為物件,則該物件即為模組的返回值。
<script data-main="./js/a.js" src="./js/require.js" ></script>
//a.js
require(['b','c','jquery'], function (b,c,$){
console.log('I am a');
b.hello();
$('#header-logo').click(function(){
c.hello();
});
});
//b.js
define(function(){
console.log('I am b');
return {
hello:function(){
console.log('click,b.js');
}
}
})
//c.js
define(function(){
console.log('I am c');
return {
hello:function(){
console.log('click,c.js');
}
}
})
引入require.js這個解析器(識別define、require等),即使要上線,用r.js合併js模組後,也依然要引入require.js。
data-main指明入口檔案。
執行結果:
I am b
I am c
I am a
click,b.js
//在點選按鈕後,會輸出:
click,c.js
可見,依賴的模組被預先載入並且預先執行,如果使用者不點選按鈕,c.js是否應該被執行呢?由於瀏覽器的環境特點,被依賴的模組肯定要預先下載的。是否需要預先執行?如果一個模組依賴了十個其他模組,那麼在本模組的程式碼執行之前,要先把其他十個模組的程式碼都執行一遍,不管這些模組是不是馬上會被用到。這個效能消耗是不容忽視的。而且,在定義模組的時候,要把所有依賴模組都羅列一遍,還要在factory中作為形參傳進去,要寫兩遍很大一串模組名稱。這是require.js被吐槽的兩個點。
解決方法:
require.js保留了CommonJS中的require、exprots、module這三個功能。可以不把依賴羅列在dependencies陣列中。而是在程式碼中用require來引入,require依然使用非同步載入、回撥。
//a.js
define(function (){//預設傳入了require、exprots、module
console.log('I am a');
require(['b'],function(b){
b.hello();
});
require(['jquery'],function($){
$('#header-logo').click(function(){
require(['c'],function(c){
c.hello();
});
});
})
});
//b.js和c.js不變
以上方法解決了上述兩個吐槽點。點選按鈕後,才載入和執行c.js,實現了懶載入。缺點是實時下載程式碼然後在回撥中才能執行,使用者體驗不好,使用者的操作會有明顯的延遲卡頓。但這樣的現實並非是無法接受的,畢竟是瀏覽器環境,我們已經習慣了操作網頁時伴隨的各種loading。
執行結果:
I am a
I am b
click,b.js
//在點選按鈕後,會輸出:
I am c
click,c.js
require.js的另一個吐槽點是使用回撥函式,這種寫法程式設計不方便。
解決方法:
require.js借鑑了“中間派”的部分優點,相容Modules/Wrappings規範。引入模組var a=require('a.js');a.hello();
但只是部分相容,例如並沒有使用module.declare來定義模組,而還是用define,模組的執行時機也沒有改變,依舊是預先執行。
//a.js
define(function (require,exports,module){
console.log('I am a');
var b=require('b');
b.hello();
require('jquery');
$('#header-logo').click(function(){
var c=require('c');
c.hello();
})
});
//b.js、c.js不變
執行結果:
I am b
I am c
I am a
click,b.js
//點選按鈕,輸出
click,c.js
注意定義模組時候的輕微差異,dependencies陣列為空,但是factory函式的形參必須手工寫上require,exports,module,(這不同於之前的dependencies和factory形參全不寫),這樣寫即可使用Simplified CommonJS wrapping風格,與commonjs的格式一致了。
雖然使用上看起來簡單,然而在理解上卻給後人埋下了一個大坑。因為AMD只是支援了這樣的語法,而並沒有真正實現模組的延後執行。上面的程式碼,正常來講應該是預先下載b.js、c.js,然後在執行模組的b.hello()方法的時候開始執行b.js裡面的程式碼,在點選按鈕的時候開始執行c.js中的方法。實際卻不是這樣,只要此模組被別的模組引入,a.js和b.js中的程式碼還是被預先執行了。
require.js中入口檔案使用define和require函式的區別:
都能執行。但是寫法不一樣,使用require函式要寫明依賴的所有模組和回撥函式,如果回撥函式內使用了require、exports、module,也要顯示寫在依賴和回撥函式的引數中。而使用define函式,require、exports、module可以省略不寫。
關於require.js的更詳細用法,會在另一篇文章中介紹。
CMD和seajs
seajs全面擁抱Modules/Wrappings規範,不用requirejs那樣回撥的方式來編寫模組。而它也不是完全按照Modules/Wrappings規範,seajs並沒有使用declare來定義模組,而是使用和requirejs一樣的define。
教程:
http://blog.codinglabs.org/articles/modularized-javascript-with-seajs.html
//html檔案中
<script src="./js/sea.js"></script>
<script>
seajs.use('./js/a');//入口檔案
</script>
//或者
<script src="./sea.js" data-main="./js/a"></script>
//a.js
define(function(require, exports, module){
console.log('a.js執行');
var b = require('b');
b.hello();
require('jquery');
$('#header-logo').click(function(){
var c = require('c');
c.hello();
});
});
//b.js
define(function(require, exports, module){
console.log('b.js執行');
return {
hello: function(){
console.log('hello, b.js');
}
}
});
//c.js
define(function(require, exports, module){
console.log('c.js執行');
return {
hello: function(){
console.log('hello, c.js');
}
}
});
執行結果:
a.js執行
b.js執行
hello, b.js
//點選按鈕,輸出
c.js執行
hello, c.js
以上程式碼實現預先下載,延遲執行。
定義模組時無需羅列依賴陣列,在factory函式中需傳入形參require,exports,module,然後它會呼叫factory函式的toString方法,對函式的內容進行正則匹配,通過匹配到的require語句來分析依賴,這樣就真正實現了commonjs風格的程式碼。
如果要實現點選下載、執行,只要改成以下程式碼即可
var c = require.async('c');
c.hello();
關於模組對外暴漏API的方式,seajs也是融合了各家之長,支援commonjs的exports.xxx = xxx和module.exports = xxx的寫法,也支援AMD的return寫法,暴露的API可以是任意型別。
ES6模組標準
對外提供API,用export匯出。
使用模組,用import關鍵字。
//a.js
import {hello,ok} from './b'
hello();
ok();
//b.js
export function hello(){
console.log('hello b.js');
}
export function ok(){
console.log('ok b.js');
}
ES6 Module的基本用法就是這樣,目前還沒有瀏覽器能支援。
我們可以使用Babel來對ES6進行編譯,轉化為可以使用的ES5程式碼。Babel 所做的只是幫你把‘ES6 模組化語法’轉化為‘CommonJS 模組化語法’,轉換後其中有require exports 等,是 CommonJS 在具體實現中所提供的變數。任何實現 CommonJS 規範的環境(如 node 環境)可以直接執行這樣的程式碼,而瀏覽器環境並沒有實現對 CommonJS 規範的支援,所以我們需要使用打包工具來進行打包,說的直觀一點就是把所有的模組組裝起來,形成一個常規的 js 檔案。這就需要我們額外使用模組打包工具,為我們的程式碼做一些包裹,讓它能在瀏覽器端使用。 比如 Browserify, Webpack。
用Browserify 和 Babel轉化和打包ES6 Module
- 全域性安裝Browserify
- 專案內安裝babelify和babel-preset-es2015
- 專案的根目錄下新建.babelrc配置檔案
presets欄位設定轉碼規則
{
"presets":["es2015"]
}
- 在package.json設定下面的程式碼,作為命令引數。
{
"browserify": {
"transform": [["babelify", { "presets": ["es2015"] }]]
}
}
- 執行命令browserify script.js -o bundle.js
上面程式碼將ES6指令碼script.js和所有引入的模組,都打包轉為bundle.js,瀏覽器直接載入後者就可以了。
Babel其他知識
- babel-cli 命令列轉碼
npm install --global babel-cli
# 轉碼結果輸出到標準輸出
$ babel example.js
# 轉碼結果寫入一個檔案
# --out-file 或 -o 引數指定輸出檔案
$ babel example.js --out-file compiled.js
# 或者
$ babel example.js -o compiled.js
# 整個目錄轉碼
# --out-dir 或 -d 引數指定輸出目錄
$ babel src --out-dir lib
# 或者
$ babel src -d lib
# -s 引數生成source map檔案
$ babel src -d lib -s
- babel-node ES6的REPL環境
- 瀏覽器環境
法1:使用babel-standalone模組提供的瀏覽器版本,將其插入網頁。
網頁中實時將ES6程式碼轉為ES5,對效能會有影響。生產環境需要載入已經轉碼完成的指令碼。使用者的ES6指令碼放在script標籤之中,但是要註明type=”text/babel”。
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.4.4/babel.min.js"></script>
<script type="text/babel">
// Your ES6 code
</script>
法2:從Babel 6.0開始,不再直接提供瀏覽器版本,而是要用構建工具構建出來。如果你沒有或不想使用構建工具,可以通過安裝5.x版本的babel-core模組獲取。
npm install [email protected]
執行上面的命令以後,就可以在當前目錄的node_modules/babel-core/子目錄裡面,找到babel的瀏覽器版本browser.js(未精簡)和browser.min.js(已精簡)。
然後,將下面的程式碼插入網頁。
<script src="node_modules/babel-core/browser.js"></script>
<script type="text/babel">
// Your ES6 code
</script>
上面程式碼中,browser.js是Babel提供的轉換器指令碼,可以在瀏覽器執行。
相關推薦
js模組化程序
模組化程序 模組就是實現特定功能的一組方法。 1、無模組時代:通過script標籤來匯入一個個的js檔案,而一個js檔案中,程式碼簡單的堆在一起,把不同的函式簡單地放在一起,就算是一個模組,只要能從上往下依次執行就可以了。帶來的問題是:全域性變數命名衝突、模
js-模組化開發總結
一.模組開發的概念 模組化開發是什麼:模組化開發是一種生產方式,這種方式生產效率高,維護成本低。從軟體開發的角度說,模組化開發是一種開發模式,寫程式碼的一種方式,開發效率高,維護成本低。 為什麼需要模組化開發:當一個專案開發的越來越複雜的時候,會遇到一些問題,比如命名衝突(重新命名),檔
JS模組化(Module模式模組化,SeaJS工具模組化)
1、Module模式模組化 Module模式具有模組化,可重用的基本特徵,封裝了變數和function,只暴露可用public的方法,其它私有方法全部隱藏。在沒有使用模組化工具的情況下,用模組化的思想來編寫整個JS結構。 例如下圖,以webrtcUI層程式碼為例,MeetingMainPag
require-js-模組化 CMD AMD
模組化的標準有了模組,我們就可以更方便地使用別人的程式碼,想要什麼功能,就載入什麼模組。這樣做有一個前提,那就是大家必須以同樣的方式編寫模組,否則你有你的寫法,我有我的寫法,豈不是亂了套! CommonJS:是一個模組化的標準,Node.js在使用的模組化標準。適用與後端開發的標準。AMD(As
點選按鈕刪除bootstrapTable選中行,js模組化及一些問題的總結
頁面效果展示 html程式碼: <div class="col-md-12" style="height: 15%"> <form action="web?module=stwmgr&action=Develop&method=se
JS模組化(一):CommonJS
模組化的規範: 目前有四種: 1.CommonJS &nbs
node.js模組化思想初探
系統模組(核心模組):node本身自帶,可以直接require的模組 自定義模組:自己寫的,以及在npm上下載的模組 一部分常用的系統模組的作用(印象) Crypto 加密 Events 事件 Net 網路操作 OS 作業系統資訊 Path 處理檔案路徑 S
【requirejs】JS模組化工具requirejs教程
初識requirejs 隨著網站功能逐漸豐富,網頁中的js也變得越來越複雜和臃腫,原有通過script標籤來匯入一個個的js檔案這種方式已經不能滿足現在網際網路開發模式,我們需要團隊協作、模組複用、單元測試等等一系列複雜的需求。 RequireJS是一個非常小巧的JavaScript模組載入框架,是A
JS模組化開發規範
JS模組化開發規範,以下介紹三種 commonJS規範(Nodejs模組系統遵循此規範,適用於服務端) 1、 規範定義 CommonJS規範規定,一個檔案就是一個模組,用module變數代表當前模組。 Node在其內部提供一個Module的構建函式。所有模組都是Module的例項 2、 module.ex
一覽js模組化:從CommonJS到ES6
本文由雲+社群發表 模組化是指把一個複雜的系統分解到一個一個的模組。 模組化開發的優點: (1)程式碼複用,讓我們更方便地進行程式碼管理、同時也便於後面程式碼的修改和維護。 (2)一個單獨的檔案就是一個模組,是一個單獨的作用域,只向外暴露特定的變數和函式。這樣可以避免汙染全域性變數,減少變數
JS模組化程式設計之AMD模型實現原理(Requirejs瀏覽器模型,nodejs伺服器模型)
官方引數手冊:https://requirejs.org/docs/api.html /** * require使用指南! * 第一步:建立一個符合Require CMD模組化的標準目錄結構,去官方檢視! * 第二步:在html檔案中指定主js檔
JS模組化之歷史演進
為什麼迫切需要js模組化 隨著2006年ajax概念被提出,前端的業務的業務越來越多,程式碼也越來越多,並逐漸發展成為前後端分離的狀態,出現了專門的前端攻城獅。 但是隨著程式碼的增多,也隨之衍生很多問題。 1.早期的js程式碼: common.js let mo
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-
JS模組化程式設計總結
一、背景 隨著的網際網路技術的不斷髮展,瀏覽器逐漸成了集大成的CS客戶端,頁面功能在向系統級、軟體級靠攏的趨勢下,開發團隊也需要一些軟體工程的方法來開發WEB專案,如測試驅動、面向物件,而模組化程式設計更是成為一種急需應用的技術。 二、原因 為方便檔案管理、增加複用
js模組化歷程
服務端向前端進軍 Modules/1.0規範源於服務端,無法直接用於瀏覽器端,原因表現為: 1. 外層沒有function包裹,變數全暴漏在全域性。如上面例子中increment.js中的add。 2. 資源的載入方式與服務端完全不同。服務端require一個模組,直接就從硬碟或者記憶體中讀取了
[前端]關於JS模組化/AMD/CMD/UMD及CSS的BEM
工作上接觸到的模組化都是比較主流的ESM和commonJS CSS上通用的是BEM 這裡主要是總結給自己看的 ESM(ES6 Module) 一個檔案一個模組 基本是webpack結合vue最常用的東西了 引入import,暴露用export import re
JS模組化規範:AMD/CMD/CommonJS
一、模組化規範的由來 隨著網頁的複雜化,javascript程式碼也相應變得龐大起來,程式碼之間的分工合作就尤為重要了,這時候有了模組,我們就可以只關注自己業務的核心邏輯,其他想要實現的功能直接載入他人的模組便足夠了。考慮到模組的統一,便有了模組規範化。接下來
JS 模組化規範
在我們最初寫程式碼的時候,引入JS檔案用script標籤來引入,並且在引入多個JS檔案時,當前檔案所依賴的JS檔案必須放在前面。也就存在一個順序的問題,而且這是由開發者去判斷和把控的。而現在前端專案越來越複雜,難免會出現很多很多script標籤引入JS,這無論對
JS模組化程式設計之AMD規範(一)
隨著網站逐漸變成"網際網路應用程式",嵌入網頁的Javascript程式碼越來越龐大,越來越複雜。 網頁越來越像桌面程式,需要一個團隊分工協作、進度管理、單元測試等等......開發者不得不使用軟體工程的方法,管理網頁的業務邏輯。 JavaScript模組化程式設
JS模組化方案(2)之CMD 模組(SeaJS)方案
一、 CMD 1、基本介紹 SeaJS 是一個適用於 Web 瀏覽器端的模組載入器。使用 SeaJS,可以更好地組織 JavaScript 程式碼。 2、實現庫:seajs 1.3.1 S