功能實現參考了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。