本文深入解析JavaScript中的享元模式,旨在解决移动端浏览器内存受限问题。通过区分内部状态与外部状态,结合工厂模式复用细粒度对象,显著减少内存开销。文章提供图书馆图书管理的完整代码示例,并对比对象池技术,展示如何在实际开发中高效管理大量相似对象。
JavaScript内存优化与享元模式概述
在JavaScript开发中,尤其是移动端浏览器环境下,系统分配的内存资源往往非常有限。因此,如何有效地节省内存空间成为了一个极具价值的技术课题。
享元模式(Flyweight Pattern)通过引入共享机制,能够高效支持大量细粒度的对象实例。其核心目的是避免因为创建大量内容相似的小类而带来的内存开销,让多个对象共享同一个类(即元类)的实例。
享元模式的主要价值在于消除大量高度相似类的冗余开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果观察到这些实例除了少数几个参数外,其余开销基本一致,就可以大幅减少需要实例化的类的数量。具体做法是将那些变化的参数提取到类实例的外部,在调用方法时作为参数传入,从而通过共享机制大幅降低单个实例的数量。
在JavaScript中应用享元模式主要有两种场景:第一种应用于数据层,处理内存中大量相似的对象;第二种应用于DOM层,例如在中央事件管理器中使用享元模式,避免为父容器内的每个子元素都单独附加事件句柄,从而提升性能。
享元模式的核心概念:内部与外部状态
Flyweight模式包含两个核心概念:内部状态(intrinsic)和外部状态(extrinsic)。内部状态存储在对象内部,由对象自身管理且不可共享;而外部状态则存储在对象外部,可以根据具体环境进行存储或删除,通常由外部客户端管理。
通俗来说,享元模式首先创建一个原始模型,随后根据不同场合和环境生成具有特定特征的具体模型。由于需要生成不同的新对象,Flyweight模式中通常结合Factory模式使用。Flyweight的内部状态用于共享,而Flyweight工厂负责维护一个Flyweight池(模式池)来存放这些内部状态对象。
我们可以将所有内部状态相同的对象替换为同一个共享对象。创建这样一个共享对象需要用到单例工厂方法,而非普通构造函数。这样做的好处是可以追踪已实例化的各个对象,确保仅当所需对象的内部状态与已有对象不同时,才创建新对象。对象的外在状态则保存在一个管理器对象中,在调用对象方法时,管理器会将这些外在状态作为参数传入。
该模式的核心思想是将一个对象的数据拆分为两部分,分别存储在两个不同的对象中:一是共享对象(即享元对象),负责存储内部状态;二是单例工厂方法,用于创建和管理共享对象;三是管理器对象,专门负责管理外部状态。
实例解析:图书馆图书管理的享元实现
以图书馆中的一本书为例,可以使用一个对象来表示它,该对象包含许多属性,如书名、作者、ISBN等。
var Book = function( id, title, author, genre, pageCount,publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate,availability ){
...//初始化代码
}
Book.prototype = {
getTitle:function(){
return this.title;
},
...
// 更新借出状态方法
updateCheckoutStatus:function(bookID, newStatus, checkoutDate,checkoutMember, newReturnDate){...},
//续借
extendCheckoutPeriod: function(bookID, newReturnDate){...},
//是否到期
isPastDue: function(bookID){...}
}
程序初期可能运行正常,但随着时间推移,图书数量可能呈批量增加,且每种图书可能有不同的版本和借阅数量。此时会发现系统性能逐渐变慢,内存中可能存在几千个book对象,造成巨大浪费。我们需要引入享元模式来进行优化。
我们可以将数据划分为内部和外部两种状态。在同一本书中,与book对象直接相关的数据(如title、author等)可以归结为内部属性;而(checkoutMember、dueReturnDate等)则可以归结为外部属性。这样,如下代码就可以在同一本书里共享同一个对象了,因为无论谁借书,只要书是同一本,其基本信息(内部状态)是一样的:
//共享对象
var Book = function(title, author, genre, pageCount, publisherID, ISBN){
this.title = title;
this.author = author;
this.genre = genre;
this.pageCount = pageCount;
this.publisherID = publisherID;
this.ISBN = ISBN;
};
接下来定义一个基本工厂,用于检查之前是否已创建该book对象。如果存在,则直接返回;如果不存在,则重新创建并存储,以便后续访问。这确保了为每一种书只创建一个对象实例:
/* Book工厂 单例 */
var BookFactory = (function(){
var existingBooks = {};
return{
createBook: function(title, author, genre,pageCount,publisherID,ISBN){
/*查找之前是否创建*/
var existingBook = existingBooks[ISBN];
if(existingBook){
return existingBook;
}else{
/* 如果没有,就创建一个,然后保存*/
var book = new Book(title, author, genre,pageCount,publisherID,ISBN);
existingBooks[ISBN] = book;
return book;
}
}
}
});
外部状态的管理相对简单。除了封装好的book对象外,其他动态数据都需要在这里进行管理。具体实现如下:
/*BookRecordManager 借书管理类 单例*/
var BookRecordManager = (function(){
var bookRecordDatabase = {};
return{
/*添加借书记录*/
addBookRecord: function(id, title, author, genre,pageCount,publisherID,ISBN, checkoutDate, checkoutMember, dueReturnDate, availability){
var book = bookFactory.createBook(title, author, genre,pageCount,publisherID,ISBN);
bookRecordDatabase[id] ={
checkoutMember: checkoutMember,
checkoutDate: checkoutDate,
dueReturnDate: dueReturnDate,
availability: availability,
book: book;
};
},
updateCheckoutStatus: function(bookID, newStatus, checkoutDate, checkoutMember, newReturnDate){
var record = bookRecordDatabase[bookID];
record.availability = newStatus;
record.checkoutDate = checkoutDate;
record.checkoutMember = checkoutMember;
record.dueReturnDate = newReturnDate;
},
extendCheckoutPeriod: function(bookID, newReturnDate){
bookRecordDatabase[bookID].dueReturnDate = newReturnDate;
},
isPastDue: function(bookID){
var currentDate = new Date();
return currentDate.getTime() > Date.parse(bookRecordDatabase[bookID].dueReturnDate);
}
};
});
通过这种方式,我们将同一种图书的相同信息保存在一个bookManager对象中,且只保存一份副本。相比之前的代码实现,可以明显发现内存占用得到了显著节约。
对比分析:对象池技术
对象池是另一种性能优化方案,它与享元模式有一些相似之处,但没有分离内部状态和外部状态这一过程。通用对象池的实现方式如下:
var objectPoolFactory = function (createObjFn) {
var objectPool = []; //对象池
return {
create: function () { //取出
var obj = objectPool.length === 0 ? createObjFn.apply(this,arguments) : objectPool.shift();
return obj;
},
recover: function (obj) { //收回
objectPool.push(obj);
}
}
};
现在,利用objectPoolFactory来创建一个装载一些iframe的对象池:
var iframeFactory = objectPoolFactory(function () {
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.onload = function () {
iframe.onload = null; //防止iframe重复加载的bug
iframeFactory.recover(iframe); //iframe加载完成后往对象池填回节点(收回)
};
return iframe;
});
//调用
var iframe1 = iframeFactory.create();
iframe1.src = 'http://www.qq.com';
参考文献:《JavaScript模式》、《JavaScript设计模式与开发实践》。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。