Leaflet源码解析系列(二):对象继承和事件机制

763 阅读10分钟

从leaflet源码解析来看面向对象设计

扩展leaflet类理论-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);

除了实现一个简单的经典继承模型之外,它还引入了几个特殊的属性,用于方便的代码组织:optionsincludestatics

  • 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;
};