Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • React

  • Vue

  • JavaScript文章

  • 学习笔记

  • openlayers

    • Openlayers用图片填充面
    • Openlayers用图片填充线
    • openlayers处理大量Overlay渲染问题
    • openlayers的比例尺
    • openlayers水印
    • openlayer实现矢量图层点击高亮效果
    • openlayers实现自定义路径
    • Openlayers加载渲染矢量切片
    • openlayers实现角度测量
    • openlayers实现长度测量
    • openlayers实现面积测量
    • Openlayers实现方位角测量
    • Openlayers中的动画
    • Openlayers的多边形高级交互
    • Openlayers地图底图换色
    • Openlayers种的默认交互事件
    • Openlayers默认键盘交互实现
    • 源码分析之Openlayers中默认键盘事件触发机制
    • 源码分析之Openlayers中的Collection类
      • 概述
      • 源码剖析
        • Collection类
        • CollectionEvent类
        • Collection类的事件机制
      • 总结
  • threejs

  • MapboxGL

  • 工具

  • 源码合集

  • 前端
  • openlayers
东流
2024-12-02
目录

源码分析之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");
      }
    }
  }
}
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
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;
  }
}
1
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;
}
1
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);
});
1
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);}
   ]
}
1
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);
  }
1
2
3
4
5
6

上述操作就会调用前面提到的Collection类中的push和remove方法,继而调用dispatchEvent方法,派发事件,根据事件类型,执行this.interactions.listeners_中的回调函数

# 总结

本文对Collection类还有Collection的事件机制进行了分析,Collection类的继承关系让其实例对象拥有一套和Map类相同的事件机制。最后以interactions为例,加深了对Collection事件机制的理解

编辑 (opens new window)
上次更新: 2024/12/03, 14:43:37
源码分析之Openlayers中默认键盘事件触发机制
threejs渲染3D字体介绍

← 源码分析之Openlayers中默认键盘事件触发机制 threejs渲染3D字体介绍→

最近更新
01
GeoJSON
05-08
02
Circle
04-15
03
CircleMarker
04-15
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式