功能实现参考了Leaflet源码。ios
咱们构造一个Class类,实现如下功能:git
JavaScirpt并非一个典型的OOP语言,因此其继承实现略为繁琐,是基于原型链的实现,但好在ES6实现了Class的语法糖,能够方便的进行继承。github
Leaflet可能为了浏览器的兼容,因此并未采用ES6的语法,同时也大量使用了[polyfill]的写法(在[Util.js]中实现)。关于polyfill,之后进行专门介绍。数组
在leaflet中,咱们能够这样写:浏览器
let Parent = Class.extend({ initialize(name) { //初始函数 this.name = name; }, greet() { console.log('hello ' + this.name); } }); let parent = new Parent('whj'); parent.greet(); // hello whj
使用L.Class.extend接收一个对象参数建立了Parent的构造函数,以后实例化调用greet函数输出hello whj。app
实际上L.Class.extend返回了一个函数(JavaScript是以函数实现类的功能)。函数
如下是实现代码:测试
function Class() {} // 声明一个函数Class Class.extend = function (props) { // 静态方法extend var NewClass = function () { if (this.initialize) { this.initialize.apply(this, arguments);//由于并不知道initialize } //传入参数数量,因此使用apply } if (props.initialize){ NewClass.prototype.initialize = props.initialize; } if (props.greet) { NewClass.prototype.greet = props.greet; } return NewClass; };
能够看见Class的静态方法extend中,声明了一个NewClass函数,以后判断参数中是否有initialize和greet,并将他们复制到NewClass的prototype中,最后返回。当对返回对象进行new操做时就会调用initialize函数。这就实现了最初代码所展示的功能。this
可是,这里传入参数限定了只有initialize或greet才能复制到其原型上,那么我传入的参数不止这两个呢?因此得对代码进行修改,使其通用化,并实现继承功能。prototype
Class.extend = function (props) { var NewClass = function () { if (this.initialize) { this.initialize.apply(this, arguments); } } //将父类的prototype取出并复制到NewClass的__super__ 静态变量中 var parentProto = NewClass.__super__ = this.prototype; var proto = Object.create(parentProto); //复制parentProto到proto中 //protos是一个新的prototype对象 proto.constructor = NewClass; NewClass.prototype = proto; //到这完成继承 extend(proto, props); //将参数复制到NewClass的prototypez中 return NewClass; };
将父类的原型prototype取出,Object.create函数返回了一个全新的父类原型prototype对象proto,将其构造函数指向当前NewClass,最后将其赋给NewClass的原型,至此完成了继承工做。注意,此时NewClass只是继承了Class。
完成继承操做以后调用extend函数将props参数复制到NewClass的原型proto上。
extend函数实现以下:
function extend(dest) { var i, j, len, src; for (j = 1, len = arguments.length; j < len; j++) { src = arguments[j]; for (i in src) { dest[i] = src[i]; } } return dest; }
须要注意的是arguments的用法,这是一个内置变量,保存着传入的全部参数,是一个类数组结构。
如今离实现继承只差一步了 (•̀ᴗ•́)و ̑̑ 。
function Class() { } Class.extend = function (props) { var NewClass = function () { ... } ... for (var i in this) { if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') { NewClass[i] = this[i]; } } ... return NewClass; };
for循环中将父类的静态方法(不在原型链上的、非prototype、非super)复制到NewClass中。
如今,基本的继承已经实现。 <(▰˘◡˘▰)>
测试代码:
let Parent = Class.extend({ initialize(name) { this.name = name; }, greet(word) { console.log(word + this.name); } }); let Child = Parent.extend({ initialize(name,age) { Parent.prototype.initialize.call(this,name); this.age = age; }, greet() { Parent.prototype.greet.call(this,this.age); } }); let child = new Child('whj',22); child.greet(); //22whj
这个功能能够在已存在的类中添加新的初始化函数,其子类也继承了这个函数。
let Parent = Class.extend({ initialize(name) { this.name = name; }, greet(word) { console.log(word + this.name); } }); // 类已构造完成 Parent.addInitHook(function () { //新增init函数 console.log("Parent's other init"); }); let parent = new Parent(); // Parent's other init
能够看见类实例化时执行了新增的init函数。
为了完成这个功能咱们在代码上进行进一步修改。
首先在Class上新增addInitHook这个方法:
Class.addInitHook = function (fn) { var init = fn; this.prototype._initHooks = this.prototype._initHooks || []; this.prototype._initHooks.push(init); return this; };
将新增函数push进_initHooks。_initHooks中的函数以后会被依次调用。
Class.extend = function (props) { var NewClass = function () { if (this.initialize) { this.initialize.apply(this, arguments); } this.callInitHooks(); // 执行调用新增的init函数的函数 } ... proto._initHooks = []; // 新增的init函数数组 proto.callInitHooks = function () { ... }; return NewClass; };
首先在原型上新增一个保存着初始化函数的数组 _initHooks、调用新增初始函数的方法
callInitHooks,最后在NewClass中调用callInitHooks。
如今看下callInitHooks的实现:
proto.callInitHooks = function () { if (this._initHooksCalled) { // 是新增函数否已被调用 return; } if (parentProto.callInitHooks) { //先调用父类的新增函数 parentProto.callInitHooks.call(this); } this._initHooksCalled = true; // 此init已被调用,标志位置为true for (var i = 0, len = proto._initHooks.length; i < len; i++) { proto._initHooks[i].call(this); // 循环调用新增的初始化函数 } };
执行这段函数时,先会递归的调用父类的callInitHooks函数,以后循环调用已构建好的
_initHooks数组中的初始函数。
首先看下示例程序:
var Parent= Class.extend({ options: { myOption1: 'foo', myOption2: 'bar' } }); var Child = Parent.extend({ options: { myOption1: 'baz', myOption3: 5 } }); var child = new Child (); child.options.myOption1; // 'baz' child.options.myOption2; // 'bar' child.options.myOption3; // 5
在父类与子类中都声明了options选项,子类继承其options并覆盖了父类同名的options。
实现以下:
Class.extend = function (props) { var NewClass = function () { ... } ... if (proto.options) { props.options = extend(proto.options, props.options); } ... return NewClass; };
这个功能有了以前的基础实现就至关简单了。判断父类是否有optios选项,如有者将子类的optios进行复制。
var MyClass = Class.extend({ statics: { FOO: 'bar', BLA: 5 } }); MyClass.FOO; // 'bar'
实现以下:
Class.extend = function (props) { var NewClass = function () { ... } ... if (props.statics) { extend(NewClass, props.statics); delete props.statics; } ... extend(proto, props); ... return NewClass; };
实现与内置选项相似,需注意的是extend执行以后得把props中的statics字段删除,以避免以后重复复制到原型上。
Mixins 是一个在旧类上添加新的属性、方法的技术。
var MyMixin = { foo: function () { console.log('foo') }, bar: 5 }; var MyClass = Class.extend({ includes: MyMixin }); // or // MyClass.include(MyMixin); var a = new MyClass(); a.foo(); // foo
实现与静态属性方法相似:
Class.extend = function (props) { var NewClass = function () { ... } ... if (props.includes) { extend.apply(null, [proto].concat(props.includes)); delete props.includes; } extend(proto, props); //将参数复制到NewClass的prototypez中 return NewClass; }; Class.include = function (props) { Util.extend(this.prototype, props); return this; };
也是一样调用了extend函数,将include复制到原型中。为何使用apply方法,主要是为了支持include为数组的状况。
Leaflet中继承功能已所有实现完成。实现思路与一些小技巧值得咱们借鉴。
这是完整实现代码。
文章首发于Whj's Website。