摘要:繼承實現在中,我們可以這樣寫初始函數使用接收一個對象參數創建了的構造函數,之后實例化調用函數輸出。完成繼承操作之后調用函數將參數復制到的原型上。
功能介紹功能實現參考了Leaflet源碼。
我們構造一個Class類,實現以下功能:
基礎的繼承功能、提供了初始化函數。
初始函數鉤子(hook)功能。
內置選項的繼承與合并。
靜態屬性方法。
Mixins。
基礎繼承 JavaScript的繼承JavaScirpt并不是一個典型的OOP語言,所以其繼承實現略為繁瑣,是基于原型鏈的實現,但好在ES6實現了Class的語法糖,可以方便的進行繼承。
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。
實際上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函數。這就實現了最初代碼所展現的功能。
但是,這里傳入參數限定了只有initialize或greet才能復制到其原型上,那么我傳入的參數不止這兩個呢?所以得對代碼進行修改,使其通用化,并實現繼承功能。
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字段刪除,以免之后重復復制到原型上。
MixinsMixins 是一個在舊類上添加新的屬性、方法的技術。
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。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/88585.html
摘要:除了以上介紹的幾種對象創建方式,此外還有寄生構造函數模式穩妥構造函數模式。 showImg(https://segmentfault.com/img/remote/1460000018196128); 面向對象 是以 對象 為中心的編程思想,它的思維方式是構造。 面向對象 編程的三大特點:封裝、繼承、多態: 封裝:屬性方法的抽象 繼承:一個類繼承(復制)另一個類的屬性/方法 多態:方...
摘要:目錄導語理解對象和面向對象的程序設計創建對象的方式的繼承機制原型對象原型鏈與原型對象相關的方法小結導語前面的系列文章,基本把的核心知識點的基本語法標準庫等章節講解完本章開始進入核心知識點的高級部分面向對象的程序設計,這一部分的內容將會對對象 目錄 導語 1.理解對象和面向對象的程序設計 2.創建對象的方式 3.JavaScript的繼承機制 3.1 原型對象 3.2 原型鏈 3.3 與...
摘要:是完全的面向對象語言,它們通過類的形式組織函數和變量,使之不能脫離對象存在。而在基于原型的面向對象方式中,對象則是依靠構造器利用原型構造出來的。 JavaScript 函數式腳本語言特性以及其看似隨意的編寫風格,導致長期以來人們對這一門語言的誤解,即認為 JavaScript 不是一門面向對象的語言,或者只是部分具備一些面向對象的特征。本文將回歸面向對象本意,從對語言感悟的角度闡述為什...
摘要:的面向對象主要包含了兩塊創建對象繼承。構造函數一般來說,我們可以這樣定義構造函數構造函數的函數名常大寫在這里,我們沒有顯示的創建對象,沒有語句,卻將屬性和方法賦值給了。 面向對象是軟件開發方法。面向對象的概念和應用已超越了程序設計和軟件開發,擴展到如數據庫系統、交互式界面、應用結構、應用平臺、分布式系統、網絡管理結構、CAD技術、人工智能等領域。面向對象是一種對現實世界理解和抽象的方法...
摘要:對象詳解對象深度剖析,深度理解對象這算是醞釀很久的一篇文章了。用空構造函數設置類名每個對象都共享相同屬性每個對象共享一個方法版本,省內存。 js對象詳解(JavaScript對象深度剖析,深度理解js對象) 這算是醞釀很久的一篇文章了。 JavaScript作為一個基于對象(沒有類的概念)的語言,從入門到精通到放棄一直會被對象這個問題圍繞。 平時發的文章基本都是開發中遇到的問題和對...
閱讀 3067·2021-11-18 10:02
閱讀 3336·2021-11-02 14:48
閱讀 3397·2019-08-30 13:52
閱讀 560·2019-08-29 17:10
閱讀 2088·2019-08-29 12:53
閱讀 1413·2019-08-29 12:53
閱讀 1034·2019-08-29 12:25
閱讀 2168·2019-08-29 12:17