js享元模式设计原理以及应用案例

更新时间:2023-07-02 19:35:18 阅读: 评论:0

js享元模式设计原理以及应⽤案例
摘要
享元模式是⽤于性能优化的设计模式之⼀,在前端编程中有重要的应⽤,尤其是在⼤量渲染DOM的时候,使⽤享元模式及对象池技术能获得极⼤优化。本⽂介绍了享元模式的概念,并将其⽤于渲染⼤量列表数据的优化上。
贝壳海鲜初识享元模式
在⾯向对象编程中,有时会重复创建⼤量相似的对象,当这些对象不能被垃圾回收的时候(⽐如被闭包在⼀个回调函数中)就会造成内存的⾼消耗,在循环体⾥创建对象时尤其会出现这种情况。享元模式提出了⼀种对象复⽤的技术,即我们不需要创建那么多对象,只需要创建若⼲个能够被复⽤的对象(享元对象),然后在实际使⽤中给享元对象注⼊差异,从⽽使对象有不同的表现。
为了要创建享元对象,⾸先要把对象的数据划分为内部状态和外部状态,具体何为内部状态,何为外部状态取决于你想要创建什么样的享元对象。
举个例⼦:
书这个类,我想创建的享元对象是“技术类书籍”,让所有技术类的书都共享这个对象,那么书的类别就是内部状态;⽽书的书名,作者可能是每本书都不⼀样的,那么书的书名和作者就是外部状态。或者换⼀种⽅式,我想创建“村上春树写的书”这种享元对象,然后让所有村上春树写的书都共享这个享元对象,此时书的作者就为内部状态。当然也可以让作者、分类同时为内部状态创建⼀个享元对象。
享元对象可以按照内部状态的不同创建若⼲个,⽐如技术类书,⽂学类书,鸡汤类书三个。在实践的时候会发现,抽象程度越⾼,所创建的享元对象就越少,但是外部状态就越多;相反抽象程度越低,所需创建的享元对象就越多,外部状态就越少。特别地,当对象的所有状态都归为内部状态时,此时每个对象都可以看作⼀个享元对象,但是没有被共享,相当于没⽤享元模式。
⼀.享元模式的结构
1.内部状态与外部状态
在享元对象内部并且不会随着环境改变⽽改变的共享部分,可以称之为享元对象的内部状态,反之随着环境改变⽽改变的,不可共享的状态称之为外部状态。
简单地说,内部状态是对象本⾝的属性,外部状态是管理这些对象所需的额外的属性,相同的对象内部状态相同,但外部状态可能不同(⽐如2本相同的书被2个⼈借⾛了)
2.享元
享元是相似对象(书的例⼦中指的是完全相同的书,⽽不是题材相似的书)间可共享的属性的集合,⽐如书的名字、作者、ISBN等等,完全相同的书这些信息都是相同的,没有必要把相同的属性在多个相似对象中保存多份,享元负责把这些属性分离出来,以便共享
如果可共享的属性⽐较复杂,还可以增加抽象享元,以及与之对应的具体享元,还可以有复合享元
3.享元⼯⼚
享元⼯⼚负责创建并管理享元,实现共享逻辑(创建时判断是否存在,已存在就返回现有对象,否则创建⼀个)
4.客户端(Client)
Client负责调⽤享元⼯⼚,并存储管理相似对象所需的额外属性(⽐如书的id,借/还⽇期,是否在馆等等)
实例:
如果需要管理的书籍数量⾮常⼤,那么使⽤享元模式节省的内存将是⼀个可观的数⽬
// 图书管理
// 书的属性:id,title,author,genre,page count,publisher id,isbn
// 管理所需的额外属性:checkout date,checkout member,due return date,availability
// 享元(存储内部状态)
function Book(title, author, genre, pageCount, publisherId, isbn){
this.title = title;
this.author = author;
< = genre;
this.pageCount = pageCount;
this.publisherId = publisherId;
this.isbn = isbn;
}
// 享元⼯⼚(创建/管理享元)
var BookFactory =(function(){
var BookFactory =(function(){
var existingBooks ={};
var existingBook =null;
return{
createBook:function(title, author, genre, pageCount, publisherId, isbn){
// 如果书籍已经创建,,则找到并返回
// !!强制返回bool类型
existingBook = existingBooks[isbn];
if(!!existingBook){
return existingBook;
}el{
// 如果不存在选择创建该书的新实例并保存
var book =new Book(title, author, genre, pageCount, publisherId, isbn);
console.log(book);
existingBooks[isbn]= book;
return book;
}
}
}
})();
// 客户端(存储外部状态)散步教学设计
var BookRecordManager =(function(){
var bookRecordDataba ={};
return{
// 添加新书到数据库
addBookRecord:function(id, title, author, genre, pageCount, publisherId, isbn,
checkoutDate, checkoutMember, dueReturnDate, availability){
var book = ateBook(title, author, genre, pageCount, publisherId, isbn);
bookRecordDataba[id]={
checkoutMember: checkoutMember,
checkoutDate: checkoutDate,
dueReturnDate: dueReturnDate,
availability: availability,
book: book
}
},
updateCheckStatus:function(bookId, newStatus, checkoutDate, checkoutMember, newReturnDate){
var record = bookRecordDataba[bookId];
record.availability = newStatus;
record.checkoutDate = checkoutDate;
record.checkoutMember = checkoutMember;
record.dueReturnDate = newReturnDate;
},
extendCheckoutPeriod:function(bookId, newReturnDate){
bookRecordDataba[bookId].dueReturnDate = newReturnDate;
},
isPastDue:function(bookId){
var currDate =new Date();
Time()> Date.par(bookRecordDataba[bookId].dueReturnDate);
}
};
})();
/
/ test
// isbn号是书籍的唯⼀标识,以下三条只会创建⼀个book对象
BookRecordManager.addBookRecord(1,'x','x','xx',300,10001,'100-232-32');// Book {title: "x", author: "x", genre: "xx", pageCount: 300, publisherId: 1 0001, …}
BookRecordManager.addBookRecord(1,'xx','xx','xx',300,10001,'100-232-32');
BookRecordManager.addBookRecord(1,'xxx','xxx','xxx',300,10001,'100-232-32');
jQuery与享元模式
jQuery.single =(function(o){
var collection =jQuery([1]);// Fill with 1 item, to make sure length === 1
return function(element){
// Give collection the element:
collection[0]= element;
// Return the collection:
return collection;
};
}());
// window.$_ = jQuery.single;  // 定义别名
// test
jQuery('a').click(function(){
var html = jQuery.single(this).next().html();// Method chaining works!
alert(html);
// etc. etc.
});
维护了⼀个单例collection,避免多次⽤$()包裹同⼀个DOM对象带来的内存消耗(会创建多个jQuery对象),使⽤jQuery.single永远都只会创
建⼀个jQuery对象,节省了创建额外jQuery对象消耗的时间,还减少了内存开销,但这样做最⼤的问题可能是:jQuery.single返回的对象⽆法
被缓存。因为内部是单例实现,缓存的对象在下⼀次调⽤jQuery.single后可能会被改变,所以⽆法像$()⼀样随时缓存。但据说直接使⽤jQuery.single 获取单例要⽐缓存普通jQuery对象更快,但为了避免混乱,建议只在需要把DOM对象包裹成jQuery对象时才使⽤jQuery.single⽅法。
要对参数进⾏处理,多态的实现
if(arguments.length ===1){
for(const item in arguments[0]){
jQuery[item]= arguments[item];
}
微课的好处
羟基磷灰石
}el{
for(const item in arguments[1]){
arguments[0][item]= arguments[1][item];// A={a:1,b:2} B={c:3,d:4} =>A.c=B.c A.d=B.d =>A={a:1,b:2,c:3,d:4} }
}
}
//把for循环变成享元模式
var target = arguments[0]||{};//健壮性的体现,出现错误不会阻塞
if(target !=='object'){
target ={}
}
var length = arguments.length;
var i =1;
if(length ==1){
target =this;//this指jQuery
i--
}
for(var item in arguments[1]){
target[item]= arguments[1][item];
}
}
享元模式的应⽤
还是以书为例⼦,实现⼀个功能:每本书都要打印出⾃⼰的书名。
先来看看没⽤享元模式之前代码的样⼦
const books =[
{name:"计算机⽹络", category:"技术类"},
{name:"算法导论", category:"技术类"},
{name:"计算机组成原理", category:"技术类"},
{name:"傲慢与偏见", category:"⽂学类"},
{name:"红与⿊", category:"⽂学类"},
{name:"围城", category:"⽂学类"}
]
class Book {
肉苁蓉的功效与禁忌
constructor(name, category){
this.name = name;
this.category = category
}
print(){
console.log(this.name,this.category)
}
}
冯迪books.forEach((bookData)=>{
const book =new Book(bookData.name, bookData.category)
const div = ateElement("div")
div.innerText = bookData.name
div.addEventListener("click",()=>{
book.print()
})
document.body.appendChild(div)
})
上⾯代码先创建了书这个对象,然后把这个对象闭包在了点击事件的回调中,可以想象,如果有⼀万本书的话,这段代码的内存开销还是很可观的。现在我们使⽤享元模式重构这段代码
思考:如果书的类别有40种,⽽作者只有10个,那么挑选哪个属性作为内部状态呢?
当然是作者,因为这样只需要创建10个享元对象就⾏了。
思考:为何不⼲脆定义⼀个没有内部状态的享元对象得了,那样只有⼀个享元对象⽤于共享?
这样当然是可以的,实际上变得跟单例模式很像,唯⼀的区别就是多了对外部状态的注⼊。
实际上内部状态越少,要注⼊的外部状态⾃然越多,⽽且为了代码的复⽤性,会让内部状态尽可能多。
const books =new Array(10000).fill(0).map((v, index)=>{
return Math.random()>0.5?{
name:`计算机科学${index}`,
category:'技术类'
}:{
name:`傲慢与偏见${index}`,
category:'⽂学类类'
}
})
class FlyweightBook {
constructor(category){
this.category = category
}
// ⽤于享元对象获取外部状态
getExternalState(state){
for(const p in state){
this[p]= state[p]
}
}
print(){
console.log(this.name,this.category)
}
}
// 然后定义⼀个⼯⼚,来为我们⽣产享元对象
// 注意,这段代码实际上⽤了单例模式,每个享元对象都为单例,因为我们没必要创建多个相同的享元对象
const flyweightBookFactory =(function(){
const flyweightBookStore ={}
return function(category){
return function(category){
if(flyweightBookStore[category]){
return flyweightBookStore[category]
}
const flyweightBook =new FlyweightBook(category)
flyweightBookStore[category]= flyweightBook
return flyweightBook
}
})()
// DOM的享元对象
class Div {
constructor(){
this.dom = ateElement("div")
}
getExternalState(extState, onClick){
/
/ 获取外部状态
this.dom.innerText = extState.innerText
// 设置DOM位置
this.p =`${extState.q *22}px`
this.dom.style.position =`absolute`
lick = onClick
}
mount(container){
container.appendChild(this.dom)
}
}
const divFactory =(function(){
const divPool =[];// 对象池
return function(innerContainer){
let div
if(divPool.length <=20){
div =new Div()
divPool.push(div)
}el{
// 滚动⾏为,在超过20个时,复⽤池中的第⼀个实例,返回给调⽤者
div = divPool.shift()
divPool.push(div)
}
王者荣耀如何隐身
return div
}
})()
// 外层container,⽤户可视区域
const container = ateElement("div")
// 内层container, 包含了所有DOM的总⾼度
const innerContainer = ateElement("div")
container.style.maxHeight ='400px'
container.style.width ='200px'
container.style.border ='1px solid'
写给男生的情书container.style.overflow ='auto'
innerContainer.style.height =`${22* books.length}px`// 由每个DOM的总⾼度算出内层container的⾼度innerContainer.style.position =`relative`
container.appendChild(innerContainer)
document.body.appendChild(container)
function load(start, end){
// 装载需要显⽰的数据
books.slice(start, end).forEach((bookData, index)=>{
// 先⽣产出享元对象
const flyweightBook =flyweightBookFactory(bookData.category)
const div =divFactory(innerContainer)
// DOM的⾼度需要由它的序号计算出来

本文发布于:2023-07-02 19:35:18,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1064986.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:对象   享元   状态   创建   模式
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图