扩展leaflet类理论-leaflet-的类、类继承和约定的概述
Class
L.Class用来新建一个新的类或者扩展已有的类,为Leaflet 提供了OOP能力,几乎所有的leaflet对象都是从class创建的。
理解Leaflet的设计理念需要对 JavaScript 的构造函数、对象原型和原型链有清晰的认识。
- 在 JavaScript 中,每个对象都有一个 proto 属性(隐式原型)指向其构造函数的原型,如下面的代码,打印结果是一个 true
- 在 JavaScript 中,每个函数都有一个 prototype 属性(显式原型),这个属性指向函数的原型对象。如前面的 Map 函数的 prototype 为 Map.prototype
- 在 JavaScript 中,每个显式原型对象都有一个 constructor 指向其构造函数本身,如下代码打印结果是一个 true
function Map() {
this.layers = []
this.center = null
this.addLayer = function (layer) {
console.log('添加图层')
}
}
//创建一个地图对象,new 的过程分为三步
var map = new Map('mapDiv');
// 1. var p={}; 初始化一个对象p。
// 2. p._proto_=Person.prototype;,将对象p的 __proto__ 属性设置为 Person.prototype
// 3. Person.call(p,”张三”,20);调用构造函数Person来初始化p。
console.log(map.__proto__ === Map.prototype);
console.log(Map === Map.prototype.constructor);
除了实现一个简单的经典继承模型之外,它还引入了几个特殊的属性,用于方便的代码组织:options、include 和 statics。
- options通常用来与父类进行属性合并,他并不会直接覆盖父类的属性;
- Include通常用来把一个方法mixin到类中;
- statics是静态的属性,经常用来义常量。
有extend,include,mergeOptions,addInitHook四个方法
- 使用 extend 方法来实现继承。子类可以访问所有的父类方法和属性。当然也可以在继承扩展的时候重写出来的方法和属性。可以通过instanceof来检测。可以通过调用父类的property上面的方法来调用父类的方法;
- 使用 include 方法用来给类扩展方法;
- 使用 mergeoptions 方法用来合并子类和父类的 options 属性;
- 使用 addInitHook 方法可以给类的初始化代码中添加钩子函数,能够实现扩展构造函数的效果。
import * as Util from './Util';
// Thanks to John Resig and Dean Edwards for inspiration!
// 就是创建一个空对象作为基类使用(构造函数,ES6 里的 Class 也是类似的语法糖)
export function Class() {}
// 实现类的继承扩展功能
Class.extend = function (props) {
// @function extend(props: Object): Function
// [Extends the current class](#class-inheritance) given the properties to be included.
// 返回一个function,作为类的构造函数 (将使用 `new` 来调用).
const NewClass = function (...args) {
Util.setOptions(this);
// 调用初始化构造函数
if (this.initialize) {
this.initialize.apply(this, args);
}
// 调用构造函数扩展钩子
this.callInitHooks();
};
// 创建超类型原型的一个副本。
const parentProto = NewClass.__super__ = this.prototype;
const proto = Object.create(parentProto);
// 为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认constructor属性。
proto.constructor = NewClass;
// 将新创建的对象(即副本)赋值给子类型的原型。
NewClass.prototype = proto;
// inherit parent's statics
// 将 Class 的静态成员 mixin 到 NewClass 里,包括静态成员函数 Extend。这也是为什么从继承 Class 的对象能使用 Extend 的原因
for (const i in this) {
if (Object.hasOwn(this, i) && i !== 'prototype' && i !== '__super__') {
NewClass[i] = this[i];
}
}
// mix static properties into the class
// 将参数 props 对象的静态成员 mixin 到 NewClass 中,实例化 NewClass 也能用到 props 对象的静态成员
if (props.statics) {
Util.extend(NewClass, props.statics);
}
// mix includes into the prototype
// 将参数 props 对象的includesMixin到proto中
if (props.includes) {
Util.extend.apply(null, [proto].concat(props.includes));
}
// mix given properties into the prototype
Util.extend(proto, props);
delete proto.statics;
delete proto.includes;
// merge options
// 合并 options
if (proto.options) {
proto.options = parentProto.options ? Object.create(parentProto.options) : {};
Util.extend(proto.options, props.options);
}
proto._initHooks = [];
// add method for calling all hooks
// 执行hooks
proto.callInitHooks = function () {
if (this._initHooksCalled) { return; }
if (parentProto.callInitHooks) {
parentProto.callInitHooks.call(this);
}
this._initHooksCalled = true;
for (let i = 0, len = proto._initHooks.length; i < len; i++) {
proto._initHooks[i].call(this);
}
};
return NewClass;
};
// @function include(properties: Object): this
// 给类增加minxin,实现类动态扩展方法的功能
Class.include = function (props) {
const parentOptions = this.prototype.options;
Util.extend(this.prototype, props);
if (props.options) {
this.prototype.options = parentOptions;
this.mergeOptions(props.options);
}
return this;
};
// @function mergeOptions(options: Object): this
// 把类默认的 option 和传入的 option 进行合并,实现类动态合并 options 的功能
Class.mergeOptions = function (options) {
Util.extend(this.prototype.options, options);
return this;
};
// @function addInitHook(fn: Function): this
// 给构造函数增加 hook,在初始化类的时候执行钩子函数,实现扩展类的构造函数的功能
Class.addInitHook = function (fn, ...args) { // (Function) || (String, args...)
const init = typeof fn === 'function' ? fn : function () {
// 创建扩展方法
this[fn].apply(this, args);
};
this.prototype._initHooks = this.prototype._initHooks || [];
this.prototype._initHooks.push(init);
return this;
};
var MyClass = L.Class.extend({
// 构造函数
initialize: function (greeter) {
this.greeter = greeter;
},
// 配置参数
options: {},
// includes可以把已有的对象作为类的方法或属性自动包含进去
includes: MyMixin,
// 自定义方法
greet: function (name) {
alert(this.greeter + ", " + name);
}
});
// 扩展已有类的构造函数(开发插件常用到此方法)
MyClass.addInitHook(function () {
// ... do something in constructor additionally
// e.g. add event listeners, set custom properties etc.
});
var MyMixin = {
foo: function () {},
bar: 5
};
// 创建一个子类,继承于该类
var MyChildClass = MyClass.extend({
// 子类的option会合并覆盖父类option
options: {},
// 可以自定义新的方法
// includes可以把已有的对象作为类的方法或属性自动包含进去
includes: MyMixin
});
// 创建一个实例,传参数进去
var bigA = new MyClass("Hello");
var sonA = new MyClass("Hello");
sonA instanceof MyChildClass; // true
sonA instanceof MyClass; // true
// 调用自定义方法
bigA.greet("World");
// 调用父类自定义方法
sonA.greet("World");
Event
Event是Leaflet中很重要的一个类,Map、Layer都是继承自它(Map和Layer上一般都注册绑定了很多交互事件)。通过on注册事件到listens中,通过fire触发事件,通过off从listens中移除并解绑事件。
import { Class } from "./Class";
import * as Util from "./Util";
/*
* @class Evented
* @aka L.Evented
* @inherits Class
*
* 定义了事件驱动(event-powered)类型的类的(例如 Map 和 Marker)一系列公共的方法。
* 通常, 当一个对象发生某些变化的时候,Event可以让对象执行指定的方法 (e.g. 当用户点击map的时候, 让地图触发 click 事件).
*
* @example
*
* ```js
* map.on('click', function(e) {
* alert(e.latlng);
* } );
* ```
*
* Leaflet 是用引函数用来响应监听函数,所以如果想在处理完一个事件后关闭对该事件的响应,最好不要用匿名函数而采用普通函数的方式:
*
* ```js
* function onClick(e) { ... }
*
* map.on('click', onClick);
* map.off('click', onClick);
* ```
*/
export const Events = {
/* @method on(type: String, fn: Function, context?: Object): this
* 给一个Object对象添加特定事件的监听函数,. 可以通过context参数设置监听函数的 ctx 上下文 (this指向)。可以通过空格间隔设置多个事件类型 (e.g. `'click dblclick'`).
*
* @alternative
* @method on(eventMap: Object): this
* 注册一系列的事件 type/listener pairs, 例如: `{click: onClick, mousemove: onMouseMove}`
*/
on(types, fn, context) {
// 可能是一个 types:handlers 键值对的map对象
if (typeof types === "object") {
for (const type in types) {
// 为了性能考虑,这里就不处理 space-separated 的 events 名称
// 这是个用的很多的方法(hot path),由于 Layer 使用了 on(obj) 格式的句法
this._on(type, types[type], fn);
}
} else {
// 处理空格,trim后按空格分割成array
types = Util.splitWords(types);
for (let i = 0, len = types.length; i < len; i++) {
this._on(types[i], fn, context);
}
}
return this;
},
/* @method off(type: String, fn?: Function, context?: Object): this
* 移除之前添加的监听函数. 如果没有指定监听函数,则会移除该事件的所有监听函数。
* 注意:如果在 on 注册监听函数的时候指定了 context,在 off 的时候必须传入相同的,context 才能移除监听函数。
*
* @alternative
* @method off(eventMap: Object): this
* Removes a set of type/listener pairs.
*
* @alternative
* @method off: this
* Removes all listeners to all events on the object. This includes implicitly attached events.
*/
off(types, fn, context) {
if (!arguments.length) {
// 如果没传参就删除event,清空所有listeners
delete this._events;
} else if (typeof types === "object") {
for (const type in types) {
this._off(type, types[type], fn);
}
} else {
types = Util.splitWords(types);
const removeAll = arguments.length === 1;
for (let i = 0, len = types.length; i < len; i++) {
if (removeAll) {
this._off(types[i]);
} else {
this._off(types[i], fn, context);
}
}
}
return this;
},
// 注册到 listener (现在没有语法糖)
_on(type, fn, context, _once) {
if (typeof fn !== "function") {
console.warn(`wrong listener type: ${typeof fn}`);
return;
}
// 检查是否已经注册过相同的事件方法,是的话不做操作
if (this._listens(type, fn, context) !== false) {
return;
}
if (context === this) {
// 更少的内存占用.
context = undefined;
}
const newListener = { fn, ctx: context };
if (_once) {
newListener.once = true;
}
this._events = this._events || {};
this._events[type] = this._events[type] || [];
this._events[type].push(newListener);
},
_off(type, fn, context) {
let listeners, i, len;
if (!this._events) {
return;
}
listeners = this._events[type];
if (!listeners) {
return;
}
if (arguments.length === 1) {
// 如果只有 type 1个参数,没有指定具体的监听函数,则移除该事件所有监听函数
if (this._firingCount) {
// 如果已经触发了监听函数,监听函数已经在执行了,则将回调函数设置成空函数,后续就不执行内容
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].fn = Util.falseFn;
}
}
// 删除该事件
delete this._events[type];
return;
}
if (typeof fn !== "function") {
console.warn(`wrong listener type: ${typeof fn}`);
return;
}
// 找到监听该函数并移除
const index = this._listens(type, fn, context);
if (index !== false) {
const listener = listeners[index];
if (this._firingCount) {
// 如果已经触发了监听函数,监听函数已经在执行了,则将该回调函数设置成空函数,后续就不执行内容
listener.fn = Util.falseFn;
/* 复制数组以防事件回调函数继续被触发 */
this._events[type] = listeners = listeners.slice();
}
listeners.splice(index, 1);
}
},
// @method fire(type: String, data?: Object, propagate?: Boolean): this
// 触发指定的事件. 可以给事件的监听函数传一个 object 对象的参数. 参数 propagate 可以控制事件是否传递给父级
fire(type, data, propagate) {
if (!this.listens(type, propagate)) {
// 没有找到监听函数
return this;
}
const event = Util.extend({}, data, {
type,
target: this,
sourceTarget: (data && data.sourceTarget) || this
});
if (this._events) {
const listeners = this._events[type];
if (listeners) {
this._firingCount = this._firingCount + 1 || 1;
// 一个事件上可能绑定了多个监听函数,遍历
for (let i = 0, len = listeners.length; i < len; i++) {
const l = listeners[i];
// off overwrites l.fn, so we need to copy fn to a var
const fn = l.fn;
if (l.once) {
// 如果是 once,立即解绑
this.off(type, fn, l.ctx);
}
// 执行监听函数(设置上下文,传入参数)
fn.call(l.ctx || this, event);
}
this._firingCount--;
}
}
if (propagate) {
// 把事件传递给父级 (set with addEventParent)
this._propagateEvent(event);
}
return this;
},
// @method listens(type: String, propagate?: Boolean): Boolean
// @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean
// 如果指定 type 的事件上存在fn的监听该函数,就返回 true.
// The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
listens(type, fn, context, propagate) {
if (typeof type !== "string") {
console.warn('"string" type argument expected');
}
// we don't overwrite the input `fn` value, because we need to use it for propagation
let _fn = fn;
if (typeof fn !== "function") {
propagate = !!fn; // ?转换结果不确定,true or false
_fn = undefined;
context = undefined;
}
// 获取 监听函数 array
const listeners = this._events && this._events[type];
if (listeners && listeners.length) {
if (this._listens(type, _fn, context) !== false) {
return true;
}
}
if (propagate) {
// also check parents for listeners if event propagates
for (const id in this._eventParents) {
if (this._eventParents[id].listens(type, fn, context, propagate)) {
return true;
}
}
}
return false;
},
// 返回监听函数的位置索引 index (number) 或者 false
_listens(type, fn, context) {
if (!this._events) {
// 没有注册过任何事件
return false;
}
// 监听函数列表
const listeners = this._events[type] || [];
if (!fn) {
return !!listeners.length;
}
if (context === this) {
// 更少的内存占用
context = undefined;
}
for (let i = 0, len = listeners.length; i < len; i++) {
if (listeners[i].fn === fn && listeners[i].ctx === context) {
return i;
}
}
return false;
},
// @method once(…): this
// Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
once(types, fn, context) {
// types can be a map of types/handlers
if (typeof types === "object") {
for (const type in types) {
// 为了性能考虑,这里就不处理 space-separated 的 events 名称
// 这是个用的很多的方法(hot path),由于 Layer 使用了 on(obj) 格式的句法
this._on(type, types[type], fn, true);
}
} else {
// types 可能是空格间隔的字符串 space-separated
types = Util.splitWords(types);
for (let i = 0, len = types.length; i < len; i++) {
this._on(types[i], fn, context, true);
}
}
return this;
},
// @method addEventParent(obj: Evented): this
// Adds an event parent - an `Evented` that will receive propagated events
addEventParent(obj) {
this._eventParents = this._eventParents || {};
this._eventParents[Util.stamp(obj)] = obj;
return this;
},
// @method removeEventParent(obj: Evented): this
// 移除父级的事件, so it will stop receiving propagated events
removeEventParent(obj) {
if (this._eventParents) {
delete this._eventParents[Util.stamp(obj)];
}
return this;
},
_propagateEvent(e) {
for (const id in this._eventParents) {
this._eventParents[id].fire(
e.type,
Util.extend(
{
layer: e.target,
propagatedFrom: e.target
},
e
),
true
);
}
}
};
// aliases; we should ditch those eventually
// @method addEventListener(…): this
// Alias to [`on(…)`](#evented-on)
Events.addEventListener = Events.on;
// @method removeEventListener(…): this
// Alias to [`off(…)`](#evented-off)
// @method clearAllEventListeners(…): this
// Alias to [`off()`](#evented-off)
Events.removeEventListener = Events.clearAllEventListeners = Events.off;
// @method addOneTimeEventListener(…): this
// Alias to [`once(…)`](#evented-once)
Events.addOneTimeEventListener = Events.once;
// @method fireEvent(…): this
// Alias to [`fire(…)`](#evented-fire)
Events.fireEvent = Events.fire;
// @method hasEventListeners(…): Boolean
// Alias to [`listens(…)`](#evented-listens)
Events.hasEventListeners = Events.listens;
export const Evented = Class.extend(Events);
Handler
handler主要是用来初始化和地图交互相关的类,是一个抽象类,内容很简单,他的实例化子类都在 ./src/map/handler/...文件夹中
import {Class} from './Class';
/*
L.Handler 是一个基础类, 在内部用于给类添加交互特性,比如给地图 Map 对象和 Marker 点对象添加拖拽功能.
*/
// @class Handler
// @aka L.Handler
// Abstract class for map interaction handlers
export const Handler = Class.extend({
initialize(map) {
this._map = map;
},
// @method enable(): this
// 启用 handler
enable() {
// 如果已经启用,直接返回
if (this._enabled) { return this; }
this._enabled = true;
this.addHooks();
return this;
},
// @method disable(): this
// 禁用 handler
disable() {
if (!this._enabled) { return this; }
this._enabled = false;
this.removeHooks();
return this;
},
// @method enabled(): Boolean
// 判断是否已经启用
enabled() {
return !!this._enabled; // 经常能看到 !! 这种格式的代码
}
// 扩展方法
// 继承自 `Handler` 的类必须实现 addHooks 和 removeHooks 两个方法:
// 当启用 handler 的时候调用 addHooks,添加事件 hooks
// 当禁用 handler 的时候调用 removeHooks,移除之前添加的事件 hooks
});
// 这个静态方法可以不必在 L.Handler 实例化的时候调用:
// 给地图 map 添加一个指定名称的handler
Handler.addTo = function (map, name) {
map.addHandler(name, this);
return this;
};