詳解JavaScript設(shè)計(jì)模式中的享元模式
概念
在《JavaScript設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》 中是這樣描述享元模式的:是一種用于性能優(yōu)化的模式。享元模式的核心是運(yùn)用共享技術(shù)來(lái)有效支持大量細(xì)粒度的對(duì)象。
如果系統(tǒng)中創(chuàng)建了大量類似的對(duì)象而導(dǎo)致內(nèi)存占用過(guò)高,享元模式就非常有用了。享元模式的目標(biāo)是盡量減少共享對(duì)象的數(shù)量
使用享元模式
享元模式要求將對(duì)象的屬性劃分為內(nèi)部狀態(tài)與外部狀態(tài)。關(guān)于如何劃分內(nèi)部狀態(tài)和外部狀態(tài),下面有幾條經(jīng)驗(yàn):
- 內(nèi)部狀態(tài)存儲(chǔ)于對(duì)象內(nèi)部
- 內(nèi)部狀態(tài)可以被一些對(duì)象共享
- 內(nèi)部狀態(tài)獨(dú)立于具體的場(chǎng)景,通常不會(huì)改變
- 外部狀態(tài)取決于具體的場(chǎng)景,并根據(jù)場(chǎng)景而變化,外部狀態(tài)不能被共享
優(yōu)化文件上傳
我們通過(guò)介紹書(shū)中文件上傳的優(yōu)化案例來(lái)說(shuō)明享元模式的使用方式和作用。在這個(gè)例子中,作者通過(guò)享元模式提升了程序的性能,接下來(lái)我們來(lái)講述這個(gè)例子。
常規(guī)方式
在上傳功能的開(kāi)發(fā)中,遇到了一個(gè)對(duì)象爆炸的問(wèn)題。上傳功能既支持依照隊(duì)列一個(gè)一個(gè)上傳,也可以支持選擇2000個(gè)文件同時(shí)上傳。在第一版開(kāi)發(fā)中,程序中將同時(shí)new了2000個(gè)upload對(duì)象,結(jié)果就是在IE瀏覽器下直接進(jìn)入假死狀態(tài)。這里,我們先寫(xiě)一下簡(jiǎn)化后的偽代碼:
var id = 0;
var Upload = function(uploadType, fileName, fileSize) {
this.uploadType = uploadType;
this.fileName = fileName;
this.fileSize = fileSize;
this.dom = null;
}
Upload.prototype.init = function(id) {
const that = this;
this.id = id;
this.dom = document.createElement('div');
this.dom.innerHTML = '<span>文件名稱:' + fileName + ', 文件大小: ' + fileSize + '</span>' +
'<button class="delFile">刪除</button>';
dom.querySelector('.delFile').onclick = function () {
that.delFile();
}
}
Upload.prototype.delFile = funtion() {
// delFile
}接下來(lái),我們將初始化上傳組件,代碼如下:
window.startUpload = function(uploadType, files) {
for (var i = 0, file; file = files[i++];) {
var uploadObj = new Upload(uploadType, file.fileName, file.fileSize);
uploadObj.init(id++)
}
}在上面的代碼中,uploadType可以支持不同類型的上傳模式,例如:瀏覽器插件、Flash和表單上傳等。當(dāng)用戶選擇好文件后,調(diào)用window下的startUpload函數(shù),在函數(shù)中將創(chuàng)建Upload對(duì)象。而實(shí)際上的Upload對(duì)象會(huì)非常大。使用這種方式我們有多少個(gè)文件就需要?jiǎng)?chuàng)建多少個(gè)Upload對(duì)象,接下來(lái)我們將使用享元模式優(yōu)化代碼。
享元模式重構(gòu)
首先,我們需要先確定
Upload對(duì)象的屬性的內(nèi)部狀態(tài)和外部狀態(tài)。
Upload對(duì)象必須依賴uploadType屬性才可以工作,只有在創(chuàng)建的時(shí)候明確了組件的類型,才可以調(diào)用各自的方法,而fileName和fileSize是根據(jù)場(chǎng)景而變化的,不能被共享。因此,我們可以確定:uploadType屬性為內(nèi)部狀態(tài),而fileName和fileSize為外部狀態(tài)。
根據(jù)上面的分析,我們首先把外部狀態(tài)剝離出來(lái),Upload函數(shù)中只保留uploadType參數(shù):
var Upload = function (uploadType) {
this.uploadType = uploadType;
}
Upload.prototype.delFile = function (id) {
uploadManager.setExternalState(id, this);
return this.dom.parentNode.removeChild(this.dom);
}我們應(yīng)該如何使用呢?之前我們可以通過(guò)init方法創(chuàng)建一個(gè)上傳組件,接下來(lái),我們需要?jiǎng)?chuàng)建兩個(gè)對(duì)象:UploadFactory用來(lái)創(chuàng)建Upload對(duì)象,uploadManager用來(lái)管理上傳對(duì)象(包括添加上傳組件和為Upload設(shè)置正確的處理文件),代碼如下:
var UploadFactory = (function () {
var createdFlyWeightObjs = {};
return {
create: function (uploadType) {
if (createdFlyWeightObjs[uploadType]) {
return createdFlyWeightObjs[uploadType];
}
return createdFlyWeightObjs[uploadType] = new Upload(uploadType);
}
}
})()
var uploadManager = (function () {
var uploadDatabase = {};
return {
add: function (id, uploadType, fileName, fileSize) {
var flyWeightObj = UploadFactory.create(uploadType);
var dom = document.createElement('div');
dom.innerHTML =
'<span>文件名稱:' + fileName + ', 文件大小: ' + fileSize + '</span>' +
'<button class="delFile">刪除</button>';
dom.querySelector('.delFile').onclick = function () {
flyWeightObj.delFile(id);
}
// 添加上傳組件
uploadDatabase[id] = {
fileName: fileName,
fileSize: fileSize,
dom: dom
};
return flyWeightObj;
},
setExternalState: function (id, flyWeightObj) {
var uploadData = uploadDatabase[id];
for (var i in uploadData) {
flyWeightObj[i] = uploadData[i];
}
}
}
})()代碼分析
UploadFactory: 根據(jù)代碼不難理解,UploadFactory中有一個(gè)create方法來(lái)創(chuàng)建Upload對(duì)象并存到createdFlyWeightObjs中,而對(duì)于相同的uploadType我們也只會(huì)創(chuàng)建一個(gè)Upload
uploadManager: 這個(gè)對(duì)象將不同文件的fileName、fileSize等屬性保存到uploadDatabase中,當(dāng)執(zhí)行setExternalStates時(shí)會(huì)將正確的文件對(duì)象設(shè)置到flyWeightObj,在執(zhí)行add時(shí),我們會(huì)創(chuàng)建Upload對(duì)象,并將上傳文件保存到uploadDatabase中
因此,當(dāng)我們需要去上傳文件時(shí),我們需要這樣修改window.startUpload,代碼如下:
var id = 0;
window.startUpload = function (uploadType, files) {
for (var i = 0, file; file = files[i++];) {
var uploadObj = uploadManager.add(++id, uploadType, file.fileName, file.fileSize);
}
};通過(guò)ploadManager.add創(chuàng)建時(shí),我們并不會(huì)有幾個(gè)文件就創(chuàng)建幾個(gè)Upload對(duì)象,相同的uploadType會(huì)共享一個(gè)Upload。當(dāng)我們執(zhí)行某一個(gè)文件的刪除時(shí):
我們會(huì)先通過(guò)uploadManager.setExternalState方法設(shè)置需要操作的文件,包括fileName、fileSize、dom,然后執(zhí)行相關(guān)的刪除邏輯,如:代碼中的是將當(dāng)前的dom刪除,即可以直接使用this.dom。
使用享元模式重構(gòu)后,就可以大量的減少創(chuàng)建Upload對(duì)象,從而實(shí)現(xiàn)性能優(yōu)化。
總結(jié)
享元模式是為了解決性能問(wèn)題而生的模式,在上述的例子中,我們通過(guò)享元模式減少了對(duì)象的創(chuàng)建從而提升了瀏覽器的性能,但在很多開(kāi)源的代碼中我還沒(méi)有找到使用享元模式的真實(shí)案例。但享元模式的思想倒是隨處可見(jiàn),例如vue、react等在處理dom元素更新時(shí)都會(huì)通過(guò)diff算法來(lái)實(shí)現(xiàn)dom節(jié)點(diǎn)的復(fù)用,從而提升一定的性能。
享元模式的適用性
最后,列出書(shū)中總結(jié)的該模式的適用性,當(dāng)以下情況發(fā)生時(shí),便可以考慮使用享元模式:
- 一個(gè)程序使用了大量的相似對(duì)象
- 由于使用了大量對(duì)象,造成很大的內(nèi)存開(kāi)銷
- 對(duì)象的大多數(shù)狀態(tài)都可以變?yōu)?strong>外部狀態(tài)
- 剝離出對(duì)象的外部狀態(tài)后,可以使用相對(duì)較少的共享對(duì)象取代大量對(duì)象
以上就是詳解JavaScript設(shè)計(jì)模式中的享元模式的詳細(xì)內(nèi)容,更多關(guān)于JavaScript 享元模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- JavaScript事件發(fā)布/訂閱模式原理與用法分析
- JavaScript實(shí)現(xiàn)與使用發(fā)布/訂閱模式詳解
- JavaScript中發(fā)布/訂閱模式的簡(jiǎn)單實(shí)例
- JS前端設(shè)計(jì)模式之發(fā)布訂閱模式詳解
- js 發(fā)布訂閱模式的實(shí)例講解
- JavaScript設(shè)計(jì)模式之觀察者模式(發(fā)布訂閱模式)原理與實(shí)現(xiàn)方法示例
- JavaScript設(shè)計(jì)模式之觀察者模式與發(fā)布訂閱模式詳解
- JavaScript設(shè)計(jì)模式之單例模式應(yīng)用場(chǎng)景案例詳解
- JavaScript 設(shè)計(jì)模式 安全沙箱模式
- JavaScript設(shè)計(jì)模式之觀察者模式(發(fā)布者-訂閱者模式)
- javascript 發(fā)布-訂閱模式 實(shí)例詳解
相關(guān)文章
小程序接口的promise化的實(shí)現(xiàn)方法
這篇文章主要介紹了小程序接口的promise化的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
localStorage實(shí)現(xiàn)便簽小程序
這篇文章主要為大家詳細(xì)介紹了localStorage實(shí)現(xiàn)便簽小程序的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
微信小程序?qū)崿F(xiàn)瀑布流分頁(yè)滾動(dòng)加載
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)瀑布流分頁(yè)滾動(dòng)加載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
CodeReview常見(jiàn)的幾個(gè)問(wèn)題梳理解決示例
這篇文章主要為大家介紹了CodeReview常見(jiàn)的幾個(gè)問(wèn)題梳理解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
JavaScript 克隆數(shù)組最簡(jiǎn)單的方法
js 樹(shù)組復(fù)制方法2009-02-02
uniapp使用H5調(diào)試時(shí)跨域問(wèn)題解決
本文主要介紹了uniapp使用H5調(diào)試時(shí)跨域問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
詳解處理bootstrap4不支持遠(yuǎn)程靜態(tài)框問(wèn)題
這篇文章主要介紹了詳解處理bootstrap4不支持遠(yuǎn)程靜態(tài)框問(wèn)題,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
如何使用瀏覽器擴(kuò)展篡改網(wǎng)頁(yè)中的JS?文件
這篇文章主要為大家介紹了如何使用瀏覽器擴(kuò)展篡改網(wǎng)頁(yè)中的JS文件實(shí)現(xiàn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05

