扩展 Leaflet
Leaflet 有数以百计的插件,这些插件扩展了 Leaflet 的功能:有时是以一种通用的方式,有时是以一种非常具体的使用方式。
有这么多插件的部分原因是 Leaflet 易于扩展。本教程将介绍最常用的方法。
请注意,本教程假设您已经很好地掌握了:
- JavaScript
- DOM handling
- Object-oriented programming (understanding concepts like classes, instances, inheritance, methods and properties)
Leaflet architecture
让我们看看 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() 实现的。这个方法可以用来 “hook” 初始化函数,在类 initialize() 之后直接运行,例如:
MyBoxClass.addInitHook(function(){
this._area = this.options.width * this.options.length;
});
这将在 initialize() 被调用后运行(调用 setOptions())。这意味着 this.options 存在,并且在 init hook 运行时有效。
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, ...) 来代替。
Factories 工厂函数
大多数 Leaflet 类都有一个相应的工厂函数。工厂函数的名称与类相同,但它使用 lowerCamelCase 而不是 UpperCamelCase:
function myBoxClass(name, options) {
return new MyBoxClass(name, options);
}
命名规范
在为 Leaflet 插件命名类时,请遵守以下命名规范:
- 永远不要在你的插件中暴露全局变量
- 如果你有一个新的类,直接把它放在
L命名空间(L.MyPlugin) - 如果你继承了一个现有的类,让它成为一个子属性(
L.TileLayer.Banana)
