Leaflet

一个开源 JavaScript 库
用于移动友好的交互式地图

← 教程

扩展 Leaflet

Leaflet 有数百个插件。这些扩展了 Leaflet 的功能:有时以通用的方式,有时以非常特定于用例的方式。

有这么多插件的部分原因是 Leaflet 很容易扩展。本教程将介绍最常用的扩展方法。

请注意,本教程假设您已经很好地掌握了

Leaflet 架构

让我们看一看 Leaflet 1.0.0 的简化 UML 类图。有超过 60 个 JavaScript 类,所以图有点大。幸运的是,我们可以使用 L.ImageOverlay 创建可缩放的图像

请查看这个独立示例。

从技术角度来看,Leaflet 可以通过多种方式进行扩展

本教程涵盖了仅在 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() );   

在命名类、方法和属性时,请遵守以下约定

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 插件命名类时,请遵守以下命名约定