源码分析之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
的长度assertUnique
assertUnique_
方法就是for.. in
循环遍历Collection
。若参数options.unique
值为true
,则在对集合Collection
每次进行添加时都会触发这个校验函数。
Collection
类方法clear
clear
方法实质上就是一个循环体,调用getLength
方法获取Collection
集合的长度,然后调用pop
方法清除最后一个元素,直到Collection
集合长度值为0
getLength
getLength
方法就是调用get
方法,该方法是在BaseObject
类中定义的,用于获取属性值。在updateLength_
就设置了Property.LENGTH
pop
因为要对
Collection
中集合项进行删除时进行一个监听,因此pop
方法不是调用数组原生的,该方法调用removeAt
,删除最后一个,并返回删除项removeAt
removeAt
方法内部首先进行了一个越界检查,然后调用 JS 数组的原生方法splice
方法删除指定项,然后调用updateLength_
方法更新Collection
的长度,再调用dispatchEvent
方法派发一个事件,最后返回删除项。forEach
forEach
方法就对Collection
进行遍历,类似于 JS 数组的原生forEach
方法getArray
getArray
方法会返回array_
,它是一个 JS 数组,可以调用 JS 原生方法,这点很重要item
item
方法返回指定项insertAt
insertAt
方法会在指定项进行添加。该方法接受两个参数,index
:索引,elem
:集合项。insertAt
方法和removeAt
方法类似,只有三处不同。- 若
unique_
为true
,则会调用assertUnique_
进行唯一性检查 - 调用
splice
方法时,参数个数和值不同,这和splice
语法有关,是为在index
处添加elem
dispatchEvent
时,实例化CollectionEvent
时,第一个参数是CollectionEventType.ADD
- 若
setAt
setAt
方法就是设置指定项的值,这个不同于insertAt
方法,setAt
方法的操作就是一个替换,该方法会dispatchEvent
两次,删除添加各一次remove
remove
方法接受一个参数,集合项elem
,然后删除该elem
.先是循环遍历找出该elem
的索引值,然后调用removeAt
进行删除。push
push
方法是一个添加操作,也会在unique_
为true
时进行唯一性校验。该方法会调用insertAt
方法进行添加,燃火返回Collection
的新长度extend
extend
方法接受一个数组,然后再循环遍历数组参数,调用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
事件机制的理解