JavaScript 设计模式:享元模式详解与文件上传实战
本文深入解析 JavaScript 中的享元模式,阐述其通过共享技术优化大量细粒度对象内存占用的核心思想。文章详细区分了内部状态与外部状态,并结合文件上传组件的实际案例,展示如何利用工厂模式管理对象池以节省浏览器内存。同时对比了对象池与直接修改形参的区别,帮助开发者在移动端等资源受限环境下做出更优的性能决策。
享元模式定义与核心思想
一、定义
享元(Flyweight)模式是一种专注于性能优化的设计模式,其核心机制在于运用共享技术来高效支持大量细粒度的对象。在 JavaScript 开发环境中,尤其是移动端浏览器,系统分配的内存资源相对有限,因此如何有效节省内存空间成为一个极具价值的优化方向。本质上,享元模式是一种通过增加少量时间开销来换取显著空间节省的策略。
- 为了直观理解,可以想象一个内衣工厂拥有 100 种男士内衣和 100 种女士内衣,需要为每种内衣拍摄照片。如果不采用享元模式,则需要准备 200 个塑料模特;而应用享元模式后,仅需男女各 1 个模特即可通过更换衣物完成所有拍摄任务。
享元模式适用场景
二、什么场景下使用享元模式?
该模式主要适用于以下两种情况:第一,程序中使用了大量相似对象,导致巨大的内存开销;第二,对象的大部分状态可以转化为外部状态,将这些外部状态剥离后,可以用相对少量的共享对象来替代大量对象。
享元模式的应用层面
三、如何应用享元模式?
享元模式的应用主要分为两个层面:一是在数据层,主要处理内存中大量相似的对象;二是在 DOM 层,例如在中央事件管理器中使用享元模式,避免为父容器内的每个子元素单独附加事件句柄,从而减少内存消耗。
内部状态与外部状态的划分
应用享元模式的关键在于将对象属性划分为内部状态和外部状态。内部状态是指那些独立于具体场景、通常不会改变且可以被多个对象共享的数据;而外部状态则依赖于具体场景,随场景变化而变化,且不能被共享。
在享元模式中,工厂模式(Factory Pattern)经常与之配合出现。Flyweight 的内部状态用于共享,而 Flyweight Factory(享元工厂)负责维护一个 Flyweight Pool(享元池),用于存放具有内部状态的共享对象。
需要注意的是,该模式也存在缺点:当系统中对象数量较少时,引入享元模式可能会增加系统的整体开销,且实现复杂度较大。
文件上传实战示例
四、示例:文件上传
var Upload = function(uploadType) {
this.uploadType = uploadType;
}
/* 删除文件(内部状态) */
Upload.prototype.delFile = function(id) {
uploadManger.setExternalState(id, this); // 把当前id对应的外部状态都组装到共享对象中
// 大于3000k提示
if(this.fileSize < 3000) {
return this.dom.parentNode.removeChild(this.dom);
}
if(window.confirm("确定要删除文件吗?" + this.fileName)) {
return this.dom.parentNode.removeChild(this.dom);
}
}
/** 工厂对象实例化
* 如果某种内部状态的共享对象已经被创建过,那么直接返回这个对象
* 否则,创建一个新的对象
*/
var UploadFactory = (function() {
var createdFlyWeightObjs = {};
return {
create: function(uploadType) {
if(createdFlyWeightObjs[uploadType]) {
return createdFlyWeightObjs[uploadType];
}
return createdFlyWeightObjs[uploadType] = new Upload(uploadType);
}
};
})();
/* 管理器封装外部状态 */
var uploadManger = (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);
};
document.body.appendChild(dom);
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];
}
}
};
})();
/*触发上传动作*/
var id = 0;
window.startUpload = function(uploadType, files) {
for(var i=0,file; file = files[i++];) {
var uploadObj = uploadManger.add(++id, uploadType, file.fileName, file.fileSize);
}
};
/* 测试 */
startUpload("plugin", [
{
fileName: '1.txt',
fileSize: 1000
},{
fileName: '2.txt',
fileSize: 3000
},{
fileName: '3.txt',
fileSize: 5000
}
]);
startUpload("flash", [
{
fileName: '4.txt',
fileSize: 1000
},{
fileName: '5.txt',
fileSize: 3000
},{
fileName: '6.txt',
fileSize: 5000
}
]);
相关概念补充与对比
五、补充
(1)直接改变形参 Demo
function f1() {
var obj = {a: 1};
f2(obj);
console.log(obj); // {a: 1, b: 2}
}
function f2(obj) {
obj.b = 2;
}
f1();
(2)对象池也是一种性能优化方案,它与享元模式有相似之处,但区别在于对象池没有分离内部状态和外部状态的过程。
var objectPoolFactory = function(createObjFn) {
var objectPool = [];
return {
create: function() {
var obj = objectPool.lenght === 0 ? createObjFn.apply(this, arguments) : objectPool.shift();
return obj;
},
recover: function() {
objectPool.push(obj);
}
};
}
希望本文所述对大家学习 javascript 程序设计有所帮助。
5款主流JS在线运行工具对比:CodePen、JSFiddle与StackBlitz怎么选
2026-11-05 17:04:04
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
JavaScript函数定义的三种方式:声明、表达式与箭头函数
2026-11-05 13:50:55
