源码分析之Openlayers中的Collection类
# 概述
在Map类中,有一种高频出现的类Collection(即集合),比如Map类中interaction、controls和overlay的定义初始化和一些操作调用都和Collection有关。本文主要介绍 Openlayers 中Collection类的实现以及Collection类的事件机制。
# 源码剖析
# Collection类
Collection类本质上就是对标准Javascript数组进行了一层封装,添加了一些方法。当对集合进行添加或者移除数组项时,会触发一个事件,比如上面示例中的CollectionEventType.ADD和CollectionEventType.REMOVE操作被监听触发后,就会执行后面的回调函数。
其实现如下:
class Collection extends BaseObject {
constructor(array, options) {
options = options || {};
this.unique_ = !!options.unique;
this.array_ = array ? array : [];
}
clear() {
while (this.getLength() > 0) {
this.pop();
}
}
extend(arr) {
for (let i = 0, ii = arr.length; i < ii; ++i) {
this.push(arr[i]);
}
return this;
}
forEach(f) {
const array = this.array_;
for (let i = 0, ii = array.length; i < ii; ++i) {
f(array[i], i, array);
}
}
getArray() {
return this.array_;
}
item(index) {
return this.array_[index];
}
getLength() {
return this.get(Property.LENGTH);
}
insertAt(index, elem) {
if (index < 0 || index > this.getLength()) {
throw new Error("Index out of bounds: " + index);
}
if (this.unique_) {
this.assertUnique_(elem);
}
this.array_.splice(index, 0, elem);
this.updateLength_();
this.dispatchEvent(
new CollectionEvent(CollectionEventType.ADD, elem, index)
);
}
pop() {
return this.removeAt(this.getLength() - 1);
}
push(elem) {
if (this.unique_) {
this.assertUnique_(elem);
}
const n = this.getLength();
this.insertAt(n, elem);
return this.getLength();
}
remove(elem) {
const arr = this.array_;
for (let i = 0, ii = arr.length; i < ii; ++i) {
if (arr[i] === elem) {
return this.removeAt(i);
}
}
return undefined;
}
removeAt(index) {
if (index < 0 || index >= this.getLength()) {
return undefined;
}
const prev = this.array_[index];
this.array_.splice(index, 1);
this.updateLength_();
this.dispatchEvent(
new CollectionEvent(CollectionEventType.REMOVE, prev, index)
);
return prev;
}
setAt(index, elem) {
const n = this.getLength();
if (index >= n) {
this.insertAt(index, elem);
return;
}
if (index < 0) {
throw new Error("Index out of bounds: " + index);
}
if (this.unique_) {
this.assertUnique_(elem, index);
}
const prev = this.array_[index];
this.array_[index] = elem;
this.dispatchEvent(
/** @type {CollectionEvent<T>} */ (
new CollectionEvent(CollectionEventType.REMOVE, prev, index)
)
);
this.dispatchEvent(
/** @type {CollectionEvent<T>} */ (
new CollectionEvent(CollectionEventType.ADD, elem, index)
)
);
}
updateLength_() {
this.set(Property.LENGTH, this.array_.length);
}
assertUnique_(elem, except) {
for (let i = 0, ii = this.array_.length; i < ii; ++i) {
if (this.array_[i] === elem && i !== except) {
throw new Error("Duplicate item added to a unique collection");
}
}
}
}
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
Collection类是继承了BaseObject类,接受两个参数array和options。array就是一个 JS 数组,而options是一个对象,其属性为unique(表示是否允许Collection集合的每一项都是唯一)
继承关系:
Collection类继承BaseObject类,BaseObject类继承Observable类,Observable类继承EventTarget类.Collection类内部方法Collection类有两个内部方法updateLength_和assertUnique_updateLength_在实例化时就会调用
updateLength_方法,该方法会调用父类中的set方法设置Property.LENGTH的值,该值表示Collections的长度assertUniqueassertUnique_方法就是for.. in循环遍历Collection。若参数options.unique值为true,则在对集合Collection每次进行添加时都会触发这个校验函数。
Collection类方法clearclear方法实质上就是一个循环体,调用getLength方法获取Collection集合的长度,然后调用pop方法清除最后一个元素,直到Collection集合长度值为0getLengthgetLength方法就是调用get方法,该方法是在BaseObject类中定义的,用于获取属性值。在updateLength_就设置了Property.LENGTHpop因为要对
Collection中集合项进行删除时进行一个监听,因此pop方法不是调用数组原生的,该方法调用removeAt,删除最后一个,并返回删除项removeAtremoveAt方法内部首先进行了一个越界检查,然后调用 JS 数组的原生方法splice方法删除指定项,然后调用updateLength_方法更新Collection的长度,再调用dispatchEvent方法派发一个事件,最后返回删除项。forEachforEach方法就对Collection进行遍历,类似于 JS 数组的原生forEach方法getArraygetArray方法会返回array_,它是一个 JS 数组,可以调用 JS 原生方法,这点很重要itemitem方法返回指定项insertAtinsertAt方法会在指定项进行添加。该方法接受两个参数,index:索引,elem:集合项。insertAt方法和removeAt方法类似,只有三处不同。- 若
unique_为true,则会调用assertUnique_进行唯一性检查 - 调用
splice方法时,参数个数和值不同,这和splice语法有关,是为在index处添加elem dispatchEvent时,实例化CollectionEvent时,第一个参数是CollectionEventType.ADD
- 若
setAtsetAt方法就是设置指定项的值,这个不同于insertAt方法,setAt方法的操作就是一个替换,该方法会dispatchEvent两次,删除添加各一次removeremove方法接受一个参数,集合项elem,然后删除该elem.先是循环遍历找出该elem的索引值,然后调用removeAt进行删除。pushpush方法是一个添加操作,也会在unique_为true时进行唯一性校验。该方法会调用insertAt方法进行添加,燃火返回Collection的新长度extendextend方法接受一个数组,然后再循环遍历数组参数,调用push方法逐个添加每项到array_上。
# CollectionEvent类
在上面Collection类中,对collection进行添加或删除时会调用dispatchEvent方法,该方法在源码分析之 Openlayers 中默认键盘事件触发机制 (opens new window)中提过。在Collection类中,dispatchEvent的参数是CollectionEvent的实例对象。
CollectionEvent类的实现如下:
export class CollectionEvent extends Event {
constructor(type, element, index) {
super(type);
this.element = element;
this.index = index;
}
}
2
3
4
5
6
7
CollectionEvent类就是继承了Event类,在其基础上多了两个参数element和index而已。
# Collection类的事件机制
Collection类的事件机制还是以interaction为例。主要也就分为三步:初始化、监听和触发
- 初始化
在通过Map类实例化时,若参数有interactions数组,这会通过Collection类包装一下,即interactions = new Collection(options.interactions.slice());;若参数没有interactions属性,则采用默认的interactions.defaults(),其部分代码如下:
export function defaults() {
const interactions = new Collection();
const keyboard = options.keyboard !== undefined ? options.keyboard : true;
if (keyboard) {
interactions.push(new KeyboardPan());
interactions.push(
new KeyboardZoom({
delta: options.zoomDelta,
duration: options.zoomDuration,
})
);
}
return interactions;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
由此可知,默认情况下Map的实例对象中interactions的值还是一个Collection实例对象。
- 监听
由Collection类的继承关系可知,每一个Collection实例对象都一个addEventListener方法,所以监听就是调用addEventListener方法,接受两个参数分别表示事件类型和回调函数。
如下:
this.interactions.addEventListener(CollectionEventType.ADD, (event) => {
event.element.setMap(this);
});
this.interactions.addEventListener(CollectionEventType.REMOVE, (event) => {
event.element.setMap(null);
});
2
3
4
5
6
7
上述代码会对this.interactions添加两个监听change:add和change:remove,然后this.interactions的listeners_的值就如下:
{
"add": [
(event) => { event.element.setMap(this);}
],
"remove": [
(event) => {event.element.setMap(null);}
]
}
2
3
4
5
6
7
8
- 触发
Map类的实例对象map会有两个方法addInteraction和removeInteraction,分别用于对实例对象的interaction进行添加和删除interaction.
addInteraction(interaction) {
this.getInteractions().push(interaction);
}
removeInteraction(interaction) {
return this.getInteractions().remove(interaction);
}
2
3
4
5
6
上述操作就会调用前面提到的Collection类中的push和remove方法,继而调用dispatchEvent方法,派发事件,根据事件类型,执行this.interactions.listeners_中的回调函数
# 总结
本文对Collection类还有Collection的事件机制进行了分析,Collection类的继承关系让其实例对象拥有一套和Map类相同的事件机制。最后以interactions为例,加深了对Collection事件机制的理解