JS中实现全局数据对象

全局是一个范围概念,可以是整个页面的全局,也可以是针对某个组件数据实例的全局,我们一定要明确这个范围边界,否则会出现变量污染的问题。

一个变量污染的案例

我写了一个折线图的组件,由于折线图其实是由很多子元素(比如坐标轴、线条、tooltip等)构成的,每个子元素都是一个独立的类,而这些类都需要用到用户的配置项(比如样式等),因此我需要一个数据对象,可以在不同的子组件之间应用,将这些用户配置传递过去。

我初始构想的,就是通过定一个全局的module来传递数据,然后我就这样写了:

全局数据对象的模块(state.js):

1
2
3
4
5
export default {
attr1: 1,
attr2:2,
......
}

修改数据(line.js):

1
2
3
4
5
6
7
import state from 'state';

class Line {
constructor() {
state.attr1 = 3;
}
}

使用修改后的数据(tooltip.js):

1
2
3
4
5
6
7
import state from 'state';

class Tooltip {
render() {
this.sth = state.attr1;
}
}

这样在页面只有一个图的时候是没问题的,但是当用户想绘制多个图的实例时,就出问题了:第一个图修改的数据,会在第二个图中生效。

原因是这个state模块,是针对代码层面的全局,也就是相当于挂载到window对象上的全局,而我们预期的,其实应该是仅针对图形实例的生命周期生效。

所以后来我重构了代码,改成了每次初始化实例时,都是使用state的深克隆对象,并将其以构造函数参数的形式传递给子组件:

组件的构造函数(line.js):

1
2
3
4
5
6
7
8
9
10
11
import state from 'state';
import Tooltip from 'tooltip';

class Line {
constructor() {
// deepClone的实现已省略
this.state = deepClone(state);
this.state.attr1 = 3;
new Tooltip(this.state);
}
}

子组件(tooltip.js):

1
2
3
4
5
6
7
8
class Tooltip {
constructor(state) {
this.state = state;
}
render() {
this.sth = this.state.attr1;
}
}