享元模式是一種用于性能優(yōu)化的模式,享元模式的核心是運(yùn)用共享技術(shù)來(lái)有效支持大量細(xì)粒度的對(duì)象。
如果系統(tǒng)中創(chuàng)建了大量類(lèi)似的對(duì)象而導(dǎo)致內(nèi)存占用過(guò)高,享元模式就非常有用了。在JavaScript中,瀏覽器特別是移動(dòng)端分配的內(nèi)存并不算多,如何節(jié)省內(nèi)存就成了一件非常有意義的事情。
為了讓我們能更好的理解享元模式,下面先說(shuō)一個(gè)例子。
享元模式案例
假設(shè)有個(gè)網(wǎng)店,目前有50件男裝和50件女裝,為了推銷(xiāo)產(chǎn)品,需要找模特穿上衣服,進(jìn)行拍攝圖片。在不適用享元模式的情況下,實(shí)現(xiàn)如下:
const Model = function (sex, wear) {
this.sex = sex
this.wear = wear
}
Model.prototype.takePhoto = function () {
console.log(`sex=${this.sex} wear=${this.wear}`)
}
for (let i = 1; i <= 50; i++) {
let maleModel = new Model('male', `wear${i}`)
maleModel.takePhoto()
let femaleModel = new Model('female', `wear${i}`)
femaleModel.takePhoto()
}
要得到一張照片,每次需要生成100個(gè)對(duì)象,如果將來(lái)有10000種衣服,那這個(gè)程序有可能因此而奔潰。
接下來(lái)我們來(lái)改造下上面的代碼:
const Model = function(sex) {
this.sex = sex
}
Model.prototype.takePhoto = function () {
console.log(`sex=${this.sex} wear=${this.wear}`)
}
const maleModel = new Model('male')
const femaleModel = new Model('female')
for (let i = 1; i <= 50; i++) {
maleModel.wear = `wear${i}`
maleModel.takePhoto()
femaleModel.wear = `wear${i}`
femaleModel.takePhoto()
}
可以看到,改性之后的代碼,只需要兩個(gè)對(duì)象便完成了同樣的功能。上面的代碼便是享元模式的雛形。
內(nèi)部狀態(tài)與外部狀態(tài)
享元模式要求將對(duì)象屬性劃分為內(nèi)部狀態(tài)和外部狀態(tài)。
享元模式的目標(biāo)是盡量減少共享對(duì)象的數(shù)量。
關(guān)于如何劃分內(nèi)部狀態(tài)和外部狀態(tài),下面是幾條大致的規(guī)則:
- 內(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)不能被共享
這樣指定內(nèi)部狀態(tài)相同的對(duì)象都指定為同一個(gè)共享的對(duì)象。而外部狀態(tài)可以從對(duì)象身上剝離出來(lái),并存儲(chǔ)在外部。
享元模式的應(yīng)用
跳轉(zhuǎn)頁(yè)面在前端開(kāi)發(fā)過(guò)程中,是最常見(jiàn)的一種操作。在頁(yè)面跳轉(zhuǎn)的時(shí)候加上一些額外的操作,也是常見(jiàn)的。下面是使用享元模式實(shí)現(xiàn)的應(yīng)用跳轉(zhuǎn):
function setExternalState (...args) {
sessionStorage.setItem('state', 1)
alert(sessionStorage.getItem('state') + args[0])
}
function Page (urls) {
this.urls = {
baidu: 'https://www.baidu.com',
taobao: 'https://www.taobao.com',
jianshu: 'http://m.itdecent.cn',
...urls
}
}
Page.prototype.jumpUrl = function (key, fn, ...args) {
fn && fn.apply(null, args)
const url = this.urls[key]
location.href = url
}
window.onload = () => {
const page = new Page()
page.jumpUrl('baidu', setExternalState, 'haha')
}