文章目录
  1. 1. 享元模式
    1. 1.1. 享元对象设计
    2. 1.2. 享元工厂
    3. 1.3. 结束语

之前讲到性能优化,大多数介绍的都是耗时上的一些优化,比如页面打开更快、用户交互响应更快等。不过,在最开始的《前端性能优化–归纳篇》一文中有说过,前端性能优化可以从两个角度来衡量:时间和空间,今天介绍的享元模式则用于空间下内存占用的优化。

享元模式

享元是一种设计模式,通过共享对象的方式来减少创建对象的数量,从而降低程序运行过程中占用的内存,提升页面性能。

一般来说,假如我们的页面中存在大量相类似的内容时,这些内容在代码中被设计为对象的方式,则我们可以通过享元的方式,将一样的对象进行共享,从而减少页面中的总对象数,降低内存占用。

本文就以最近比较熟练的表格为例子来介绍吧。

享元对象设计

假设我们现在有 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#000000rgb(0,0,0)都是代表一种颜色,那么在key中如何让它们保持一致呢?

结束语

之前将性能优化都倾向于介绍比较大的解决方案,后面如果有时间的话,也会考虑一个个小的优化点拎出来简单讲讲,比如享元就是其中一个。

有时候一点小小的问题里,也会有许多学问可以学习的!

码生艰难,写文不易,给我家猪囤点猫粮了喵~

B站: 被删

查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢

如果你想要关注日常生活中的我,欢迎关注“牧羊的猪”公众号噢

作者:被删

出处:https://godbasin.github.io

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

文章目录
  1. 1. 享元模式
    1. 1.1. 享元对象设计
    2. 1.2. 享元工厂
    3. 1.3. 结束语