Leaflet

一个开源并且对移动端友好的
交互式地图 JavaScript 库

← 教程

扩展 Leaflet

Leaflet 有数以百计的插件,这些插件扩展了 Leaflet 的功能:有时是以一种通用的方式,有时是以一种非常具体的使用方式。

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

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

Leaflet architecture

让我们看看 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() 实现的。这个方法可以用来 “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 插件命名类时,请遵守以下命名规范: