Skip to content

基础知识

fuzhenn edited this page Oct 12, 2017 · 6 revisions

本文内容包括插件开发的基础知识, 基础类方法, 和一个简单插件示例.

目录

  1. 简介
  2. 基础知识
  3. 第一个插件

简介

maptalks.js核心库没有的功能, 都能开发为插件.

将功能包装为插件的益处:

  • 方便重用
  • 与业务逻辑解耦
  • 易于测试, 更健壮

插件可用于:

  • 为已有类增加新的方法
  • 创建新的图层
  • 为图层创建新的渲染器
  • 创建新的图形
  • 创建新的地图工具
  • 创建新的地图控件
  • 创建新的地图UI组件

你可以在这里浏览maptalks已有的插件, 列表里的插件还在不断增加, 也期待你能与大家分享你的插件作品.

maptalks设计之初就将插件作为最重要的设计目标, 为其开发插件是一件轻松愉悦的工作, 那么让我们马上开始插件开发之旅.

基础知识

在开始开发插件之前, 你需要了解:

  • ES6(ES2015)语法
  • node.js的安装与使用
  • 构建工具 gulp
  • rollup
  • babel

面向对象

maptalks是面向对象的开发库, 除少数工具方法, 所有功能均以类(class)的形式发布.

如果你对面向对象的javascript不熟悉, 可以阅读下面这篇文章.

maptalks.Class

Leaflet启发, 我们创建了maptalks.Class类, 定义了一些常用类操作方法, 开发插件时, 可以把maptalks.Class作为根类(当然这不是必须的).

maptalks.Class中定义的类操作方法(源代码)如下:

class Class {

    // 构造函数
    // 参数中的options将与类的默认options合并, 生成对象的options
    constructor(options) { }

    // 将参数中的options与对象已有的options合并
    setOptions(options) { }

    // 返回或修改对象的options
    // * 如果key为空, 则返回对象当前的options, 例如
    //     const options = map.config(); 
    // * 如果key不为空, 则修改对象的options
    //   * key可以是键值, 例如map.config('draggable', false);
    //   * key可以是对象, 例如
    //     map.config({
    //       'draggable' : false,
    //       'doubleClickZoom' : false
    //     });
    config(key) { }

    // 用config更新options时的回调函数
    onConfig(key) { }

    // 添加一个创建钩子函数, 创建对象时, 钩子函数会被调用
    static addInitHook(fn, ...args) { }
   
    // 在类上增加新的方法
    static include(...sources)

    // 定义类的默认options
    // 如果默认options已存在, 则与其合并
    static mergeOptions(options)
}

options

options是一个特殊属性, 当你设置options时, 它不会覆盖类或父类的options而是与其合并. options特别适合用来管理类的配置项与配置项默认值.

options的用法:

  • 在类上定义默认的options:
class MyClass extends maptalks.Class { }
// 定义MyClass上的默认options
MyClass.mergeOptions({
    option1: 'foo',
    option2: 'bar'
});

class ChildClass extends MyClass { }
ChildClass.mergeOptions({
    option1: 'blah',
    option3: true
});

const a = new ChildClass();
a.options.option1; // 'blah', 覆盖了父类的option1
a.options.option2; // 'bar', 父类定义的option2
a.options.option3; // true
  • 除默认options, 也能在类构造函数中传入options, 创建对象时, options将与类默认options合并, 例如:
class MyClass extends maptalks.Class {
    constructor(options) {
        super(options);
    }
};
MyClass.mergeOptions({
    foo: 'bar',
    bla: 5
});

const a = new MyClass({bla: 10});

a.options; // {foo: 'bar', bla: 10}
  • 对象创建后, 用config方法来设置options, 例如:
/*
class MyClass extends maptalks.Class {
    constructor(options) {
        super(options);
    }
};
MyClass.mergeOptions({
    foo: 'bar',
    bla: 5
});
*/
const a = new MyClass({bla: 10});

a.config({
    bla : 20
});

a.config('foo', 'barar');

a.options; // {foo: 'barar', bla: 20}

继承

我们采用ES6标准语法来继承父类, 创建子类, 例如:

class Child extends maptalks.Class {
    constructor(name, options) {
        super(options);
        this.name = name;
    }
}

Mixin

Mixin是多重继承的一种实现方式, 我们采用MDN推荐的方式实现Mixin

const calculatorMixin = Base => class extends Base {
  calc() { }
};

const randomizerMixin = Base => class extends Base {
  randomize() { }
};

class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }

maptalks中的Mixin有Eventable, JSONAble

include方法

include类似Mixin, 可以用来在类上定义新的类方法, 例如

class MyClass extends maptalks.Class {}
MyClass.include({
    fooFunc() {
        //...
    }
});

const a = new MyClass();
a.fooFunc();

include与Mixin不同的是, 一般用include定义只在MyClass中使用的方法, Mixin用于多重继承.

addInitHook方法

在插件开发时, 如果需要在创建类的对象时执行一些附加逻辑, 此时可以用addInitHook方法添加钩子方法, 添加的钩子方法会在执行类的构造函数时被调用, 例如:

MyClass.addInitHook(function () {
    this.foo = 'bar';
});

addInitHook也支持下面的形式, methodNameOfMyClass会在构造函数执行时被调用, arg1, arg2等会作为参数传递给methodNameOfMyClass

MyClass.include({
    methodNameOfMyClass() { }
});
MyClass.addInitHook('methodNameOfMyClass', arg1, arg2, );

第一个插件

以上即是maptalks插件开发所需的基础知识, 接下来, 我们来看一个实际的插件示例(github地址).

该插件用来判断多边形是否存在自相交(因自相交图形会增加复杂度, 大多数时候是不允许的). 插件在maptalks的PolygonMultiPolygon图形类上增加了isects方法, 该方法没有参数, 其返回值是个数组, 如果存在自相交, 数组中会包含相交点的坐标, 如果没有自相交, 数组即为空, 其调用示例:

const polygon = new maptalks.Polygon(...);
const isects = polygon.isects();
console.log(isects.length > 0 ? '有自相交' : '没有自相交');

插件基于2d-polygon-self-intersections实现. 我们通过上面提到的include方法来给Polygon和MultiPolygon增加了新方法, 完整源代码如下:

import isect from '2d-polygon-self-intersections';
import * as maptalks from 'maptalks';

maptalks.Polygon.include({
    isects() {
        const coordinates = maptalks.Coordinate.toNumberArrays(this.getCoordinates());
        const sects = [];
        let r, ring;
        for (let i = 0, l = coordinates.length; i < l; i++) {
            ring = coordinates[i];
            if (ring.length > 0) {
                ring = ring.slice(0, ring.length - 1);
            }
            r = isect(ring);
            if (r.length > 0) {
                sects.push([i, r]);
            }
        }
        return sects;
    }
});

maptalks.MultiPolygon.include({
    isects() {
        const geometries = this.getGeometries();
        let r;
        const sects = [];
        for (let i = 0, l = geometries.length; i < l; i++) {
            r = geometries[i].isects();
            if (r.length > 0) {
                sects.push([i, r]);
            }
        }
        return sects;
    }
});

接下来请继续阅读其他文章, 你会学习到插件开发的更多技巧.