核心架构
本文档描述了支撑所有 Leaflet 组件的基础架构模式。它涵盖了类系统、事件处理、核心工具函数以及构成地图、图层、控件和处理器基础的坐标抽象。
有关 LeafletMap 组件本身的信息,请参阅地图组件。有关事件传播和模式的详细信息,请参阅事件系统。有关辅助函数和 Class 基类,请参阅工具与类系统。有关坐标转换和投影,请参阅地理坐标与投影。
基础类层次结构
Leaflet 的架构建立在少量基类之上,这些基类通过继承提供通用功能。Class 基类提供了面向对象编程的基础,而 Evented 扩展了它以添加几乎所有 Leaflet 对象都使用的事件功能。
图示:核心类层次结构与继承链
类系统
Class 基类提供了 Leaflet 的继承机制和选项管理。所有 Leaflet 类都继承自 Class 或其子类之一。
关键机制
| 机制 | 静态方法 | 实例方法 | 用途 |
|---|---|---|---|
| 选项 | setDefaultOptions(options) | this.options | 将默认选项合并到原型上 |
| 初始化 | addInitHook(fn) | callInitHooks() | 在构造后运行钩子 |
| 混入 | include(props) | - | 向类原型添加方法 |
| 合并 | mergeOptions(options) | - | 将额外选项合并到默认值中 |
初始化流程
当构造一个 Leaflet 对象时,会发生以下序列:
选项继承
选项通过原型链继承。当调用 setOptions 时,它会创建一个新的选项对象,该对象继承自父级的选项:
事件系统 (Evented)
Evented 扩展了 Class 以提供发布-订阅事件系统。大多数 Leaflet 类继承自 Evented 以实现事件驱动架构。
事件注册与触发
图示:事件生命周期
事件处理器存储
事件处理器存储在 _events 对象中,按事件类型键控。每种事件类型映射到一个监听器对象数组:
// 内部结构
this._events = {
'click': [
{fn: handlerFn1, ctx: contextObj1, once: false},
{fn: handlerFn2, ctx: undefined, once: true}
],
'move': [
{fn: moveHandler, ctx: undefined, once: false}
]
}上下文优化
当使用 context === this 注册监听器时,上下文被设置为 undefined 以减少内存占用,因为默认行为无论如何都会使用 this 调用:
事件父级与传播
Leaflet 支持通过事件父级进行分层事件传播。当使用 propagate=true 调用 fire() 时,事件会通过注册父级对象冒泡:
事件父级使用 Util.stamp() 生成唯一 ID 存储在 _eventParents 中:
核心工具函数
Util 命名空间提供了整个 Leaflet 使用的辅助函数。这些是纯函数,没有状态。
基本工具函数
| 函数 | 签名 | 用途 |
|---|---|---|
stamp(obj) | Object → Number | 通过 _leaflet_id 属性分配并返回唯一 ID |
setOptions(obj, options) | Object, Object → Object | 将选项合并到 obj.options,返回合并后的选项 |
throttle(fn, time, context) | Function, Number, Object → Function | 返回节流函数,每 time 毫秒最多执行一次 |
template(str, data) | String, Object → String | 使用数据对象计算模板字符串 'Hello {name}' |
formatNum(num, precision) | Number, Number → Number | 将数字四舍五入到 precision 位小数(默认 6) |
splitWords(str) | String → String[] | 在空白字符处修剪并分割字符串 |
wrapNum(num, range, includeMax) | Number, Number[], Boolean → Number | 通过取模将数字包装在范围内 |
图示:Util.stamp() 使用模式
模板系统
template() 函数支持简单的字符串插值,用于瓦片 URL 生成和其他动态字符串:
坐标系统与几何
Leaflet 在渲染管道的不同空间中使用不同的坐标类型。理解这些类型是使用该库的基础。
坐标类型层次结构
坐标转换管道
LeafletMap 类提供了不同坐标空间之间的转换方法:
| 方法 | 从 | 到 | 描述 |
|---|---|---|---|
latLngToLayerPoint(latlng) | LatLng | Point | 地理 → 图层点(相对于像素原点) |
layerPointToLatLng(point) | Point | LatLng | 图层点 → 地理 |
latLngToContainerPoint(latlng) | LatLng | Point | 地理 → 容器点(相对于地图容器) |
containerPointToLatLng(point) | Point | LatLng | 容器点 → 地理 |
project(latlng, zoom) | LatLng | Point | 地理 → 缩放级别下的投影点 |
unproject(point, zoom) | Point | LatLng | 投影点 → 地理 |
图示:坐标空间转换
点与边界操作
Point 和 Bounds 类提供了向量数学和几何操作:
架构模式
初始化钩子模式
Leaflet 使用初始化钩子允许子类注入行为而无需覆盖构造函数。这实现了清晰的组合:
使用示例:
// 在 LeafletMap 类定义中
LeafletMap.addInitHook('_initContainer', id);
LeafletMap.addInitHook('_initLayout');
LeafletMap.addInitHook('_initEvents');
// 替代函数形式
LeafletMap.addInitHook(function () {
if (this.options.maxBounds) {
this.setMaxBounds(this.options.maxBounds);
}
});选项合并模式
Leaflet 的选项系统使用原型继承,允许在类层次结构的每个级别设置默认值:
事件父级模式
事件父级模式支持分层事件冒泡,通常用于图层组内的图层:
与 LeafletMap 集成
基础类集成形成完整的地图系统。LeafletMap 类展示了这种集成:
关键集成点:
- 类系统:LeafletMap 使用
setDefaultOptions()定义默认地图选项,使用addInitHook()注册初始化步骤 - 事件系统:LeafletMap 扩展 Evented 以触发
'move'、'zoom'、'click'等事件并传播图层事件 - 工具函数:LeafletMap 使用
Util.stamp()生成图层 ID,Util.setOptions()合并选项,Util.throttle()优化性能 - 坐标:LeafletMap 使用 CRS 转换提供 LatLng 和 Point 空间之间的转换方法
测试模式
核心架构经过广泛测试以确保可靠性:
| 测试套件 | 测试 | 关键断言 |
|---|---|---|
ClassSpec.js | 类继承、选项合并、初始化钩子 | 选项正确继承,钩子按顺序运行 |
EventsSpec.js | 事件注册、触发、传播、上下文 | 监听器以正确上下文调用,传播正常工作 |
UtilSpec.js | 工具函数 | stamp() 唯一性,throttle() 计时,template() 插值 |
MapSpec.js | 地图初始化、视图管理、坐标转换 | 地图状态正确,转换准确 |