詳解JavaScript設(shè)計模式中的享元模式
概念
在《JavaScript設(shè)計模式與開發(fā)實踐》 中是這樣描述享元模式的:是一種用于性能優(yōu)化的模式。享元模式的核心是運用共享技術(shù)來有效支持大量細粒度的對象。
如果系統(tǒng)中創(chuàng)建了大量類似的對象而導(dǎo)致內(nèi)存占用過高,享元模式就非常有用了。享元模式的目標是盡量減少共享對象的數(shù)量
使用享元模式
享元模式要求將對象的屬性劃分為內(nèi)部狀態(tài)與外部狀態(tài)。關(guān)于如何劃分內(nèi)部狀態(tài)和外部狀態(tài),下面有幾條經(jīng)驗:
- 內(nèi)部狀態(tài)存儲于對象內(nèi)部
- 內(nèi)部狀態(tài)可以被一些對象共享
- 內(nèi)部狀態(tài)獨立于具體的場景,通常不會改變
- 外部狀態(tài)取決于具體的場景,并根據(jù)場景而變化,外部狀態(tài)不能被共享
優(yōu)化文件上傳
我們通過介紹書中文件上傳的優(yōu)化案例來說明享元模式的使用方式和作用。在這個例子中,作者通過享元模式提升了程序的性能,接下來我們來講述這個例子。
常規(guī)方式
在上傳功能的開發(fā)中,遇到了一個對象爆炸的問題。上傳功能既支持依照隊列一個一個上傳,也可以支持選擇2000個文件同時上傳。在第一版開發(fā)中,程序中將同時new
了2000個upload
對象,結(jié)果就是在IE瀏覽器下直接進入假死狀態(tài)。這里,我們先寫一下簡化后的偽代碼:
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 }
接下來,我們將初始化上傳組件,代碼如下:
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和表單上傳等。當用戶選擇好文件后,調(diào)用window下的startUpload
函數(shù),在函數(shù)中將創(chuàng)建Upload
對象。而實際上的Upload
對象會非常大。使用這種方式我們有多少個文件就需要創(chuàng)建多少個Upload
對象,接下來我們將使用享元模式優(yōu)化代碼。
享元模式重構(gòu)
首先,我們需要先確定
Upload
對象的屬性的內(nèi)部狀態(tài)和外部狀態(tài)。
Upload
對象必須依賴uploadType
屬性才可以工作,只有在創(chuàng)建的時候明確了組件的類型,才可以調(diào)用各自的方法,而fileName
和fileSize
是根據(jù)場景而變化的,不能被共享。因此,我們可以確定:uploadType
屬性為內(nèi)部狀態(tài),而fileName
和fileSize
為外部狀態(tài)。
根據(jù)上面的分析,我們首先把外部狀態(tà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)該如何使用呢?之前我們可以通過init
方法創(chuàng)建一個上傳組件,接下來,我們需要創(chuàng)建兩個對象:UploadFactory
用來創(chuàng)建Upload
對象,uploadManager
用來管理上傳對象(包括添加上傳組件和為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
中有一個create
方法來創(chuàng)建Upload
對象并存到createdFlyWeightObjs
中,而對于相同的uploadType
我們也只會創(chuàng)建一個Upload
uploadManager
: 這個對象將不同文件的fileName、fileSize
等屬性保存到uploadDatabase
中,當執(zhí)行setExternalStates
時會將正確的文件對象設(shè)置到flyWeightObj
,在執(zhí)行add
時,我們會創(chuàng)建Upload
對象,并將上傳文件保存到uploadDatabase
中
因此,當我們需要去上傳文件時,我們需要這樣修改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); } };
通過ploadManager.add
創(chuàng)建時,我們并不會有幾個文件就創(chuàng)建幾個Upload
對象,相同的uploadType
會共享一個Upload
。當我們執(zhí)行某一個文件的刪除時:
我們會先通過uploadManager.setExternalState
方法設(shè)置需要操作的文件,包括fileName、fileSize、dom
,然后執(zhí)行相關(guān)的刪除邏輯,如:代碼中的是將當前的dom
刪除,即可以直接使用this.dom
。
使用享元模式重構(gòu)后,就可以大量的減少創(chuàng)建Upload
對象,從而實現(xiàn)性能優(yōu)化。
總結(jié)
享元模式
是為了解決性能問題而生的模式,在上述的例子中,我們通過享元模式
減少了對象的創(chuàng)建從而提升了瀏覽器的性能,但在很多開源的代碼中我還沒有找到使用享元模式
的真實案例。但享元模式的思想倒是隨處可見,例如vue、react等在處理dom元素更新時都會通過diff算法來實現(xiàn)dom節(jié)點的復(fù)用,從而提升一定的性能。
享元模式的適用性
最后,列出書中總結(jié)的該模式的適用性,當以下情況發(fā)生時,便可以考慮使用享元模式
:
- 一個程序使用了大量的相似對象
- 由于使用了大量對象,造成很大的內(nèi)存開銷
- 對象的大多數(shù)狀態(tài)都可以變?yōu)?strong>外部狀態(tài)
- 剝離出對象的外部狀態(tài)后,可以使用相對較少的共享對象取代大量對象
以上就是詳解JavaScript設(shè)計模式中的享元模式的詳細內(nèi)容,更多關(guān)于JavaScript 享元模式的資料請關(guān)注腳本之家其它相關(guān)文章!
- JavaScript事件發(fā)布/訂閱模式原理與用法分析
- JavaScript實現(xiàn)與使用發(fā)布/訂閱模式詳解
- JavaScript中發(fā)布/訂閱模式的簡單實例
- JS前端設(shè)計模式之發(fā)布訂閱模式詳解
- js 發(fā)布訂閱模式的實例講解
- JavaScript設(shè)計模式之觀察者模式(發(fā)布訂閱模式)原理與實現(xiàn)方法示例
- JavaScript設(shè)計模式之觀察者模式與發(fā)布訂閱模式詳解
- JavaScript設(shè)計模式之單例模式應(yīng)用場景案例詳解
- JavaScript 設(shè)計模式 安全沙箱模式
- JavaScript設(shè)計模式之觀察者模式(發(fā)布者-訂閱者模式)
- javascript 發(fā)布-訂閱模式 實例詳解