openlayers源码分析(1)

1 准备

// 码云fork,速度快
git clone https://gitee.com/solfKwolf/openlayers.git
// 以v5.3版本为准
git checkout v5.3.0

2 入口文件

src/ol/index.js

/**
 * @module ol
 */

export {default as AssertionError} from './AssertionError.js';
export {default as Collection} from './Collection.js';
export {default as Disposable} from './Disposable.js';
export {default as Feature} from './Feature.js';
export {default as Geolocation} from './Geolocation.js';
export {default as Graticule} from './Graticule.js';
export {default as Image} from './Image.js';
export {default as ImageBase} from './ImageBase.js';
export {default as ImageCanvas} from './ImageCanvas.js';
export {default as ImageTile} from './ImageTile.js';
export {default as Kinetic} from './Kinetic.js';
export {default as Map} from './Map.js';
export {default as MapBrowserEvent} from './MapBrowserEvent.js';
export {default as MapBrowserEventHandler} from './MapBrowserEventHandler.js';
export {default as MapBrowserPointerEvent} from './MapBrowserPointerEvent.js';
export {default as MapEvent} from './MapEvent.js';
export {default as Object} from './Object.js';
export {default as Observable} from './Observable.js';
export {default as Overlay} from './Overlay.js';
export {default as PluggableMap} from './PluggableMap.js';
export {default as Tile} from './Tile.js';
export {default as TileCache} from './TileCache.js';
export {default as TileQueue} from './TileQueue.js';
export {default as TileRange} from './TileRange.js';
export {default as VectorImageTile} from './VectorImageTile.js';
export {default as VectorTile} from './VectorTile.js';
export {default as View} from './View.js';
export {default as WebGLMap} from './WebGLMap.js';

export {getUid, inherits, VERSION} from './util.js';

ol是将不同的模块拆分成不同的文件集中放置在ol文件下,再统一通过index.js入口文件暴露出去。

将default暴露的模块重命名

// xxx.js
export default function() {
  // statement
}
// index.js
export {default as xxx} from './xxx.js';

// 相当于
import { default as xxx } from './xxx.js'

3 AssertionError

openlayer自定义错误类(继承Error),目的是方便自定义错误描述与错误码相对应。

示例截图

class AssertionError extends Error {

  /**
   * @param {number} code Error code.
   */
  constructor(code) {
    const path = VERSION === 'latest' ? VERSION : 'v' + VERSION.split('-')[0];
    const message = 'Assertion failed. See https://openlayers.org/en/' + path +
    '/doc/errors/#' + code + ' for details.';

    super(message);

    /**
     * Error code. The meaning of the code can be found on
     * https://openlayers.org/en/latest/doc/errors/ (replace `latest` with
     * the version found in the OpenLayers script's header comment if a version
     * other than the latest is used).
     * @type {number}
     * @api
     */
    this.code = code;

    /**
     * @type {string}
     */
    this.name = 'AssertionError';

    // Re-assign message, see https://github.com/Rich-Harris/buble/issues/40
    this.message = message;
  }

}

如果需要自己开发类库,需要认真研读Error类,非常简单。

5 Event

openlayers库中定义的事件基类,只提供typetarget属性以及stopPropagationpreventDefault方法。方便后面的自定义事件。

class Event {

  /**
   * @param {string} type Type.
   */
  constructor(type) {

    /**
     * @type {boolean}
     */
    this.propagationStopped;

    /**
     * The event type.
     * @type {string}
     * @api
     */
    this.type = type;

    /**
     * The event target.
     * @type {Object}
     * @api
     */
    this.target = null;
  }

  /**
   * Stop event propagation.
   * @api
   */
  preventDefault() {
    this.propagationStopped = true;
  }

  /**
   * Stop event propagation.
   * @api
   */
  stopPropagation() {
    this.propagationStopped = true;
  }

}


/**
 * @param {Event|import("./Event.js").default} evt Event
 */
export function stopPropagation(evt) {
  evt.stopPropagation();
}


/**
 * @param {Event|import("./Event.js").default} evt Event
 */
export function preventDefault(evt) {
  evt.preventDefault();
}

export default Event;

6 Target

对象类是配合事件类的。

class Target extends Disposable {
  constructor() {

    super();

    /**
     * @private
     * @type {!Object<string, number>}
     */
    this.pendingRemovals_ = {};

    /**
     * @private
     * @type {!Object<string, number>}
     */
    this.dispatching_ = {};

    /**
     * @private
     * @type {!Object<string, Array<import("../events.js").ListenerFunction>>}
     */
    this.listeners_ = {};

  }

  /**
   * @param {string} type Type.
   * @param {import("../events.js").ListenerFunction} listener Listener.
   */
  addEventListener(type, listener) {
    let listeners = this.listeners_[type];
    if (!listeners) {
      listeners = this.listeners_[type] = [];
    }
    if (listeners.indexOf(listener) === -1) {
      listeners.push(listener);
    }
  }

  /**
   * Dispatches an event and calls all listeners listening for events
   * of this type. The event parameter can either be a string or an
   * Object with a `type` property.
   *
   * @param {{type: string,
   *     target: (EventTargetLike|undefined),
   *     propagationStopped: (boolean|undefined)}|
   *     import("./Event.js").default|string} event Event object.
   * @return {boolean|undefined} `false` if anyone called preventDefault on the
   *     event object or if any of the listeners returned false.
   * @api
   */
  dispatchEvent(event) {
    const evt = typeof event === 'string' ? new Event(event) : event;
    const type = evt.type;
    evt.target = this;
    const listeners = this.listeners_[type];
    let propagate;
    if (listeners) {
      if (!(type in this.dispatching_)) {
        this.dispatching_[type] = 0;
        this.pendingRemovals_[type] = 0;
      }
      ++this.dispatching_[type];
      for (let i = 0, ii = listeners.length; i < ii; ++i) {
        if (listeners[i].call(this, evt) === false || evt.propagationStopped) {
          propagate = false;
          break;
        }
      }
      --this.dispatching_[type];
      if (this.dispatching_[type] === 0) {
        let pendingRemovals = this.pendingRemovals_[type];
        delete this.pendingRemovals_[type];
        while (pendingRemovals--) {
          this.removeEventListener(type, VOID);
        }
        delete this.dispatching_[type];
      }
      return propagate;
    }
  }

  /**
   * @inheritDoc
   */
  disposeInternal() {
    unlistenAll(this);
  }

  /**
   * Get the listeners for a specified event type. Listeners are returned in the
   * order that they will be called in.
   *
   * @param {string} type Type.
   * @return {Array<import("../events.js").ListenerFunction>} Listeners.
   */
  getListeners(type) {
    return this.listeners_[type];
  }

  /**
   * @param {string=} opt_type Type. If not provided,
   *     `true` will be returned if this event target has any listeners.
   * @return {boolean} Has listeners.
   */
  hasListener(opt_type) {
    return opt_type ?
      opt_type in this.listeners_ :
      Object.keys(this.listeners_).length > 0;
  }

  /**
   * @param {string} type Type.
   * @param {import("../events.js").ListenerFunction} listener Listener.
   */
  removeEventListener(type, listener) {
    const listeners = this.listeners_[type];
    if (listeners) {
      const index = listeners.indexOf(listener);
      if (type in this.pendingRemovals_) {
        // make listener a no-op, and remove later in #dispatchEvent()
        listeners[index] = VOID;
        ++this.pendingRemovals_[type];
      } else {
        listeners.splice(index, 1);
        if (listeners.length === 0) {
          delete this.listeners_[type];
        }
      }
    }
  }
}


export default Target;

7 EventType

事件类型字典表

export default {
  /**
   * Generic change event. Triggered when the revision counter is increased.
   * @event module:ol/events/Event~Event#change
   * @api
   */
  CHANGE: 'change',

  CLEAR: 'clear',
  CONTEXTMENU: 'contextmenu',
  CLICK: 'click',
  DBLCLICK: 'dblclick',
  DRAGENTER: 'dragenter',
  DRAGOVER: 'dragover',
  DROP: 'drop',
  ERROR: 'error',
  KEYDOWN: 'keydown',
  KEYPRESS: 'keypress',
  LOAD: 'load',
  MOUSEDOWN: 'mousedown',
  MOUSEMOVE: 'mousemove',
  MOUSEOUT: 'mouseout',
  MOUSEUP: 'mouseup',
  MOUSEWHEEL: 'mousewheel',
  MSPOINTERDOWN: 'MSPointerDown',
  RESIZE: 'resize',
  TOUCHSTART: 'touchstart',
  TOUCHMOVE: 'touchmove',
  TOUCHEND: 'touchend',
  WHEEL: 'wheel'
};

7 BaseObject

src/BaseObject.js这个对象其实使用频率最高,为啥,因为经常用到它定义的get的方法

8 Collection.js

这里定义了CollectionCollectionEvent2个类

CollectionEvent

只在Event的基础上增加了element属性

/**
 * @classdesc
 * Events emitted by {@link module:ol/Collection~Collection} instances are instances of this
 * type.
 */
export class CollectionEvent extends Event {

  /**
   * @param {CollectionEventType} type Type.
   * @param {*=} opt_element Element.
   */
  constructor(type, opt_element) {
    super(type);

    /**
     * The element that is added to or removed from the collection.
     * @type {*}
     * @api
     */
    this.element = opt_element;

  }
}

Collection

对js数组的扩展,简单的一个数据接口,可以参考下。

class Collection extends BaseObject {

  /**
   * @param {Array<T>=} opt_array Array.
   * @param {Options=} opt_options Collection options.
   */
  constructor(opt_array, opt_options) {

    super();

    const options = opt_options || {};

    /**
     * @private
     * @type {boolean}
     */
    this.unique_ = !!options.unique;

    /**
     * @private
     * @type {!Array<T>}
     */
    this.array_ = opt_array ? opt_array : [];

    if (this.unique_) {
      for (let i = 0, ii = this.array_.length; i < ii; ++i) {
        this.assertUnique_(this.array_[i], i);
      }
    }

    this.updateLength_();

  }

  /**
   * Remove all elements from the collection.
   * @api
   */
  clear() {
    while (this.getLength() > 0) {
      this.pop();
    }
  }

  /**
   * Add elements to the collection.  This pushes each item in the provided array
   * to the end of the collection.
   * @param {!Array<T>} arr Array.
   * @return {Collection<T>} This collection.
   * @api
   */
  extend(arr) {
    for (let i = 0, ii = arr.length; i < ii; ++i) {
      this.push(arr[i]);
    }
    return this;
  }

  /**
   * Iterate over each element, calling the provided callback.
   * @param {function(T, number, Array<T>): *} f The function to call
   *     for every element. This function takes 3 arguments (the element, the
   *     index and the array). The return value is ignored.
   * @api
   */
  forEach(f) {
    const array = this.array_;
    for (let i = 0, ii = array.length; i < ii; ++i) {
      f(array[i], i, array);
    }
  }

  /**
   * Get a reference to the underlying Array object. Warning: if the array
   * is mutated, no events will be dispatched by the collection, and the
   * collection's "length" property won't be in sync with the actual length
   * of the array.
   * @return {!Array<T>} Array.
   * @api
   */
  getArray() {
    return this.array_;
  }

  /**
   * Get the element at the provided index.
   * @param {number} index Index.
   * @return {T} Element.
   * @api
   */
  item(index) {
    return this.array_[index];
  }

  /**
   * Get the length of this collection.
   * @return {number} The length of the array.
   * @observable
   * @api
   */
  getLength() {
    return this.get(Property.LENGTH);
  }

  /**
   * Insert an element at the provided index.
   * @param {number} index Index.
   * @param {T} elem Element.
   * @api
   */
  insertAt(index, elem) {
    if (this.unique_) {
      this.assertUnique_(elem);
    }
    this.array_.splice(index, 0, elem);
    this.updateLength_();
    this.dispatchEvent(
      new CollectionEvent(CollectionEventType.ADD, elem));
  }

  /**
   * Remove the last element of the collection and return it.
   * Return `undefined` if the collection is empty.
   * @return {T|undefined} Element.
   * @api
   */
  pop() {
    return this.removeAt(this.getLength() - 1);
  }

  /**
   * Insert the provided element at the end of the collection.
   * @param {T} elem Element.
   * @return {number} New length of the collection.
   * @api
   */
  push(elem) {
    if (this.unique_) {
      this.assertUnique_(elem);
    }
    const n = this.getLength();
    this.insertAt(n, elem);
    return this.getLength();
  }

  /**
   * Remove the first occurrence of an element from the collection.
   * @param {T} elem Element.
   * @return {T|undefined} The removed element or undefined if none found.
   * @api
   */
  remove(elem) {
    const arr = this.array_;
    for (let i = 0, ii = arr.length; i < ii; ++i) {
      if (arr[i] === elem) {
        return this.removeAt(i);
      }
    }
    return undefined;
  }

  /**
   * Remove the element at the provided index and return it.
   * Return `undefined` if the collection does not contain this index.
   * @param {number} index Index.
   * @return {T|undefined} Value.
   * @api
   */
  removeAt(index) {
    const prev = this.array_[index];
    this.array_.splice(index, 1);
    this.updateLength_();
    this.dispatchEvent(new CollectionEvent(CollectionEventType.REMOVE, prev));
    return prev;
  }

  /**
   * Set the element at the provided index.
   * @param {number} index Index.
   * @param {T} elem Element.
   * @api
   */
  setAt(index, elem) {
    const n = this.getLength();
    if (index < n) {
      if (this.unique_) {
        this.assertUnique_(elem, index);
      }
      const prev = this.array_[index];
      this.array_[index] = elem;
      this.dispatchEvent(
        new CollectionEvent(CollectionEventType.REMOVE, prev));
      this.dispatchEvent(
        new CollectionEvent(CollectionEventType.ADD, elem));
    } else {
      for (let j = n; j < index; ++j) {
        this.insertAt(j, undefined);
      }
      this.insertAt(index, elem);
    }
  }

  /**
   * @private
   */
  updateLength_() {
    this.set(Property.LENGTH, this.array_.length);
  }

  /**
   * @private
   * @param {T} elem Element.
   * @param {number=} opt_except Optional index to ignore.
   */
  assertUnique_(elem, opt_except) {
    for (let i = 0, ii = this.array_.length; i < ii; ++i) {
      if (this.array_[i] === elem && i !== opt_except) {
        throw new AssertionError(58);
      }
    }
  }
}

Disposable

需要被清理的对象

class Disposable {

  constructor() {
    /**
     * The object has already been disposed.
     * @type {boolean}
     * @private
     */
    this.disposed_ = false;
  }

  /**
   * Clean up.
   */
  dispose() {
    if (!this.disposed_) {
      this.disposed_ = true;
      this.disposeInternal();
    }
  }

  /**
   * Extension point for disposable objects.
   * @protected
   */
  disposeInternal() {}
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,306评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,657评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,928评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,688评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,105评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,024评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,159评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,937评论 0 212
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,689评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,851评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,325评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,651评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,364评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,192评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,985评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,154评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,955评论 2 279

推荐阅读更多精彩内容