By
被删
更新日期:
之前讲到性能优化,大多数介绍的都是耗时上的一些优化,比如页面打开更快、用户交互响应更快等。不过,在最开始的《前端性能优化–归纳篇》一文中有说过,前端性能优化可以从两个角度来衡量:时间和空间,今天介绍的享元模式则用于空间下内存占用的优化。
享元模式
享元是一种设计模式,通过共享对象的方式来减少创建对象的数量,从而降低程序运行过程中占用的内存,提升页面性能。
一般来说,假如我们的页面中存在大量相类似的内容时,这些内容在代码中被设计为对象的方式,则我们可以通过享元的方式,将一样的对象进行共享,从而减少页面中的总对象数,降低内存占用。
本文就以最近比较熟练的表格为例子来介绍吧。
享元对象设计
假设我们现在有 1W 个单元格的表格,每个单元格内都有不一样的文字信息,但是单元格格式基本上都是一样的,这里包括字体色、背景色、对齐方式等等格式。
我们可以将这样一个格式CellStyle
作为享元对象,它的属性可能包括:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const enum HorizontalAlign { left = "left", center = "center", right = "right", }
const enum VerticalAlign { top = "top", middle = "middle", bottom = "bottom", }
interface ICellStyleProps { textColor: string; backgroundColor: string; horizontalAlign: HorizontalAlign; verticalAlign: VerticalAlign; }
|
那么一个CellStyle
对象则可能是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class CellStyle { private textColor: string; private backgroundColor: string; private horizontalAlign: HorizontalAlign; private verticalAlign: VerticalAlign;
private constructor({ textColor: string, backgroundColor: string, horizontalAlign: HorizontalAlign, verticalAlign: VerticalAlign, }) { this.textColor = textColor || "#000"; this.backgroundColor = backgroundColor || "#fff"; this.horizontalAlign = horizontalAlign || HorizontalAlign.left; this.verticalAlign = verticalAlign || VerticalAlign.middle; }
get textColor() { return this.textColor; }
get backgroundColor() { return this.backgroundColor; }
get horizontalAlign() { return this.horizontalAlign; }
get verticalAlign() { return this.verticalAlign; } }
|
一个单元格可能是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Cell { private row: number; private column: number;
private cellStyle: CellStyle; private text: string;
private constructor( row: number, column: number, text: string, cellStyle?: CellStyle ) { this.row = row; this.column = column; this.text = text; this.cellStyle = cellStyle || new CellSyle(); } }
|
由于单元格跟行列信息row/column
挂钩,因此是无法完全享元的,那么 1W 个单元格的表格里可能有 1W 个Cell
对象。同样的,每个Cell
对象都有一个CellStyle
对象,因此该表格同样会有 1W 个CellStyle
对象。
但是CellStyle
对象仅跟单元格的格式相关,我们可以考虑将CellStyle
对象进行享元。
享元工厂
我们可以给CellStyle
定义一个享元的key
,当然这个key
可以代表完全相同格式的CellStyle
对象,并通过享元的方式创建对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| class CellStyle { static pools: { [key: string]: CellStyle; } = {};
static generateKey( textColor: string, backgroundColor: string, horizontalAlign: HorizontalAlign, verticalAlign: VerticalAlign ) { return `${textColor}-${backgroundColor}-${horizontalAlign}-${verticalAlign}`; }
static newInstance(props: ICellStyleProps): CellStyle { const { textColor, backgroundColor, horizontalAlign, verticalAlign } = props; const key = CellStyle.generateKey( textColor, backgroundColor, horizontalAlign, verticalAlign );
const cellStyle = CellStyle.pools[key];
return cellStyle ? cellStyle : (CellStyle.pools[key] = new CellStyle( textColor, backgroundColor, horizontalAlign, verticalAlign )); }
private textColor: string; private backgroundColor: string; private horizontalAlign: HorizontalAlign; private verticalAlign: VerticalAlign;
private constructor( textColor: string, backgroundColor: string, horizontalAlign: HorizontalAlign, verticalAlign: VerticalAlign ) { this.textColor = textColor || "#000"; this.backgroundColor = backgroundColor || "#fff"; this.horizontalAlign = horizontalAlign || HorizontalAlign.left; this.verticalAlign = verticalAlign || VerticalAlign.middle; }
get textColor() { return this.textColor; }
get backgroundColor() { return this.backgroundColor; }
get horizontalAlign() { return this.horizontalAlign; }
get verticalAlign() { return this.verticalAlign; } }
|
相比于new CellStyle()
的方式创建对象,我们可以使用CellStyle.newInstance()
的方式来创建:
1 2 3 4 5 6
| const cellStyle = CellStyle.newInstance({ textColor: "#000", backgroundColor: "#fff", horizontalAlign: HorizontalAlign.center, verticalAlign: VerticalAlign.top, });
|
到这里,如果我们表格中 1W 个单元格的样式都是一样的,那么我们页面中只会存在一个CellStyle
对象,大幅度减少了对象的创建和维护,降低了页面的内存占用,从而提升页面的性能。
当然,享元并不是万能的。前端性能优化的尽头往往是时间换空间、空间换时间,享元便是一个时间换空间的典型例子,我们通过key
去获取对象时会比直接访问要多一步。
除此之外,享元对象需要十分注意对象的修改。由于对象是享元的,如果在使用的时候直接修改了,会导致许多引用到的地方都被修改。因此,一般建议通过CellStyle.newInstance()
新建CellStyle
对象的方式来进行修改。
最后其实还留了个小问题给小伙伴们想一想,CellStyle
中颜色是字符串的形式提供的,但前端颜色表示可能不只有一个,比如#000
、#000000
和rgb(0,0,0)
都是代表一种颜色,那么在key
中如何让它们保持一致呢?
结束语
之前将性能优化都倾向于介绍比较大的解决方案,后面如果有时间的话,也会考虑一个个小的优化点拎出来简单讲讲,比如享元就是其中一个。
有时候一点小小的问题里,也会有许多学问可以学习的!
查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢
如果你想要关注日常生活中的我,欢迎关注“牧羊的猪”公众号噢