扩展 Leaflet
Leaflet 有数百个插件。这些扩展了 Leaflet 的功能:有时以通用的方式,有时以非常特定于用例的方式。
有这么多插件的部分原因是 Leaflet 很容易扩展。本教程将介绍最常用的扩展方法。
请注意,本教程假设您已经很好地掌握了
- JavaScript
- DOM 处理
- 面向对象编程(了解类、实例、继承、方法和属性等概念)
Leaflet 架构
让我们看一看 Leaflet 1.0.0 的简化 UML 类图。有超过 60 个 JavaScript 类,所以图有点大。幸运的是,我们可以使用 L.ImageOverlay
创建可缩放的图像
请查看这个独立示例。 |
从技术角度来看,Leaflet 可以通过多种方式进行扩展
- 最常见的方法:使用
L.Class.extend()
创建L.Layer
、L.Handler
或L.Control
的新子类- 图层在移动/缩放地图时会移动
- 处理程序是不可见的,并解释浏览器事件
- 控件是固定的界面元素
- 使用
L.Class.include()
在现有类中包含更多功能。- 添加新的方法和选项
- 更改某些方法
- 使用
addInitHook
运行额外的构造函数代码。
- 使用
L.Class.include()
更改现有类的部分内容(替换类方法的工作方式)。
本教程涵盖了仅在 Leaflet 1.0.0 中可用的一些类和方法。如果您正在为早期版本开发插件,请谨慎使用。
L.Class
JavaScript 是一种有点奇怪的语言。它不是真正的面向对象语言,而是 原型面向对象语言。这使得 JavaScript 在历史上很难在经典的 OOP 意义上使用类继承。
Leaflet 通过 L.Class
来解决这个问题,它简化了类继承。
即使现代 JavaScript 可以使用 ES6 类,Leaflet 并没有围绕它们进行设计。
L.Class.extend()
要创建 Leaflet 中任何内容的子类,请使用 .extend()
方法。它接受一个参数:一个包含键值对的普通对象,每个键都是属性或方法的名称,每个值都是属性的初始值,或者方法的实现
var MyDemoClass = L.Class.extend({
// A property with initial value = 42
myDemoProperty: 42,
// A method
myDemoMethod: function() { return this.myDemoProperty; }
});
var myDemoInstance = new MyDemoClass();
// This will output "42" to the development console
console.log( myDemoInstance.myDemoMethod() );
在命名类、方法和属性时,请遵守以下约定
- 函数、方法、属性和工厂名称应使用
lowerCamelCase
。 - 类名应使用
UpperCamelCase
。 - 私有属性和方法以下划线 (
_
) 开头。这不会使它们成为私有的,只是建议开发人员不要直接使用它们。
L.Class.include()
如果一个类已经定义,可以使用 .include()
重新定义现有的属性/方法,或者添加新的属性/方法
MyDemoClass.include({
// Adding a new property to the class
_myPrivateProperty: 78,
// Redefining a method
myDemoMethod: function() { return this._myPrivateProperty; }
});
var mySecondDemoInstance = new MyDemoClass();
// This will output "78"
console.log( mySecondDemoInstance.myDemoMethod() );
// However, properties and methods from before still exist
// This will output "42"
console.log( mySecondDemoInstance.myDemoProperty );
L.Class.initialize()
在 OOP 中,类有一个构造函数方法。在 Leaflet 的 L.Class
中,构造函数方法始终命名为 initialize
。
如果您的类有一些特定的 options
,最好在构造函数中使用 L.setOptions()
初始化它们。此实用程序函数将提供的选项与类的默认选项合并。
var MyBoxClass = L.Class.extend({
options: {
width: 1,
height: 1
},
initialize: function(name, options) {
this.name = name;
L.setOptions(this, options);
}
});
var instance = new MyBoxClass('Red', {width: 10});
console.log(instance.name); // Outputs "Red"
console.log(instance.options.width); // Outputs "10"
console.log(instance.options.height); // Outputs "1", the default
Leaflet 以特殊的方式处理 options
属性:父类可用的选项将被子类继承:。
var MyCubeClass = MyBoxClass.extend({
options: {
depth: 1
}
});
var instance = new MyCubeClass('Blue');
console.log(instance.options.width); // Outputs "1", parent class default
console.log(instance.options.height); // Outputs "1", parent class default
console.log(instance.options.depth); // Outputs "1"
子类通常会运行父类的构造函数,然后再运行自己的构造函数。在 Leaflet 中,这可以通过 L.Class.addInitHook()
实现。此方法可用于“挂钩”在类 initialize()
之后立即运行的初始化函数,例如
MyBoxClass.addInitHook(function(){
this._area = this.options.width * this.options.length;
});
这将在调用 initialize()
(调用 setOptions()
)之后运行。这意味着当 init 挂钩运行时,this.options
存在并且有效。
addInitHook
有一个备用语法,它使用方法名称并且可以填写方法参数
MyCubeClass.include({
_calculateVolume: function(arg1, arg2) {
this._volume = this.options.width * this.options.length * this.options.depth;
}
});
MyCubeClass.addInitHook('_calculateVolume', argValue1, argValue2);
父类的方法
调用父类的方法是通过进入父类的原型并使用 Function.call(…)
来实现的。这可以在 L.FeatureGroup
的代码中看到,例如
L.FeatureGroup = L.LayerGroup.extend({
addLayer: function (layer) {
…
L.LayerGroup.prototype.addLayer.call(this, layer);
},
removeLayer: function (layer) {
…
L.LayerGroup.prototype.removeLayer.call(this, layer);
},
…
});
调用父类的构造函数的方式类似,但使用 ParentClass.prototype.initialize.call(this, …)
而不是。
工厂
大多数 Leaflet 类都有一个相应的 工厂函数。工厂函数与类具有相同的名称,但使用 lowerCamelCase
而不是 UpperCamelCase
function myBoxClass(name, options) {
return new MyBoxClass(name, options);
}
命名约定
在为 Leaflet 插件命名类时,请遵守以下命名约定
- 不要在插件中公开全局变量。
- 如果您有新的类,请直接将其放在
L
命名空间中 (L.MyPlugin
)。 - 如果您继承了现有的类之一,请将其设为子属性 (
L.TileLayer.Banana
)。