摘要:繼承實(shí)現(xiàn)在中,我們可以這樣寫初始函數(shù)使用接收一個(gè)對(duì)象參數(shù)創(chuàng)建了的構(gòu)造函數(shù),之后實(shí)例化調(diào)用函數(shù)輸出。完成繼承操作之后調(diào)用函數(shù)將參數(shù)復(fù)制到的原型上。
功能介紹功能實(shí)現(xiàn)參考了Leaflet源碼。
我們構(gòu)造一個(gè)Class類,實(shí)現(xiàn)以下功能:
基礎(chǔ)的繼承功能、提供了初始化函數(shù)。
初始函數(shù)鉤子(hook)功能。
內(nèi)置選項(xiàng)的繼承與合并。
靜態(tài)屬性方法。
Mixins。
基礎(chǔ)繼承 JavaScript的繼承JavaScirpt并不是一個(gè)典型的OOP語言,所以其繼承實(shí)現(xiàn)略為繁瑣,是基于原型鏈的實(shí)現(xiàn),但好在ES6實(shí)現(xiàn)了Class的語法糖,可以方便的進(jìn)行繼承。
Leaflet可能為了瀏覽器的兼容,所以并未采用ES6的語法,同時(shí)也大量使用了[polyfill]的寫法(在[Util.js]中實(shí)現(xiàn))。關(guān)于polyfill,以后進(jìn)行專門介紹。
繼承實(shí)現(xiàn)在leaflet中,我們可以這樣寫:
let Parent = Class.extend({ initialize(name) { //初始函數(shù) this.name = name; }, greet() { console.log("hello " + this.name); } }); let parent = new Parent("whj"); parent.greet(); // hello whj
使用L.Class.extend接收一個(gè)對(duì)象參數(shù)創(chuàng)建了Parent的構(gòu)造函數(shù),之后實(shí)例化調(diào)用greet函數(shù)輸出hello whj。
實(shí)際上L.Class.extend返回了一個(gè)函數(shù)(JavaScript是以函數(shù)實(shí)現(xiàn)類的功能)。
以下是實(shí)現(xiàn)代碼:
function Class() {} // 聲明一個(gè)函數(shù)Class Class.extend = function (props) { // 靜態(tài)方法extend var NewClass = function () { if (this.initialize) { this.initialize.apply(this, arguments);//因?yàn)椴⒉恢纈nitialize } //傳入?yún)?shù)數(shù)量,所以使用apply } if (props.initialize){ NewClass.prototype.initialize = props.initialize; } if (props.greet) { NewClass.prototype.greet = props.greet; } return NewClass; };
可以看見Class的靜態(tài)方法extend中,聲明了一個(gè)NewClass函數(shù),之后判斷參數(shù)中是否有initialize和greet,并將他們復(fù)制到NewClass的prototype中,最后返回。當(dāng)對(duì)返回對(duì)象進(jìn)行new操作時(shí)就會(huì)調(diào)用initialize函數(shù)。這就實(shí)現(xiàn)了最初代碼所展現(xiàn)的功能。
但是,這里傳入?yún)?shù)限定了只有initialize或greet才能復(fù)制到其原型上,那么我傳入的參數(shù)不止這兩個(gè)呢?所以得對(duì)代碼進(jìn)行修改,使其通用化,并實(shí)現(xiàn)繼承功能。
Class.extend = function (props) { var NewClass = function () { if (this.initialize) { this.initialize.apply(this, arguments); } } //將父類的prototype取出并復(fù)制到NewClass的__super__ 靜態(tài)變量中 var parentProto = NewClass.__super__ = this.prototype; var proto = Object.create(parentProto); //復(fù)制parentProto到proto中 //protos是一個(gè)新的prototype對(duì)象 proto.constructor = NewClass; NewClass.prototype = proto; //到這完成繼承 extend(proto, props); //將參數(shù)復(fù)制到NewClass的prototypez中 return NewClass; };
將父類的原型prototype取出,Object.create函數(shù)返回了一個(gè)全新的父類原型prototype對(duì)象proto,將其構(gòu)造函數(shù)指向當(dāng)前NewClass,最后將其賦給NewClass的原型,至此完成了繼承工作。注意,此時(shí)NewClass只是繼承了Class。
完成繼承操作之后調(diào)用extend函數(shù)將props參數(shù)復(fù)制到NewClass的原型proto上。
extend函數(shù)實(shí)現(xiàn)如下:
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的用法,這是一個(gè)內(nèi)置變量,保存著傳入的所有參數(shù),是一個(gè)類數(shù)組結(jié)構(gòu)。
現(xiàn)在離實(shí)現(xiàn)繼承只差一步了 (?????)? ?? 。
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循環(huán)中將父類的靜態(tài)方法(不在原型鏈上的、非prototype、非super)復(fù)制到NewClass中。
現(xiàn)在,基本的繼承已經(jīng)實(shí)現(xiàn)。 <(?????)>
測(cè)試代碼:
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初始函數(shù)鉤子
這個(gè)功能可以在已存在的類中添加新的初始化函數(shù),其子類也繼承了這個(gè)函數(shù)。
let Parent = Class.extend({ initialize(name) { this.name = name; }, greet(word) { console.log(word + this.name); } }); // 類已構(gòu)造完成 Parent.addInitHook(function () { //新增init函數(shù) console.log("Parent"s other init"); }); let parent = new Parent(); // Parent"s other init
可以看見類實(shí)例化時(shí)執(zhí)行了新增的init函數(shù)。
為了完成這個(gè)功能我們?cè)诖a上進(jìn)行進(jìn)一步修改。
首先在Class上新增addInitHook這個(gè)方法:
Class.addInitHook = function (fn) { var init = fn; this.prototype._initHooks = this.prototype._initHooks || []; this.prototype._initHooks.push(init); return this; };
將新增函數(shù)push進(jìn)_initHooks。_initHooks中的函數(shù)之后會(huì)被依次調(diào)用。
Class.extend = function (props) { var NewClass = function () { if (this.initialize) { this.initialize.apply(this, arguments); } this.callInitHooks(); // 執(zhí)行調(diào)用新增的init函數(shù)的函數(shù) } ... proto._initHooks = []; // 新增的init函數(shù)數(shù)組 proto.callInitHooks = function () { ... }; return NewClass; };
首先在原型上新增一個(gè)保存著初始化函數(shù)的數(shù)組 _initHooks、調(diào)用新增初始函數(shù)的方法
callInitHooks,最后在NewClass中調(diào)用callInitHooks。
現(xiàn)在看下callInitHooks的實(shí)現(xiàn):
proto.callInitHooks = function () { if (this._initHooksCalled) { // 是新增函數(shù)否已被調(diào)用 return; } if (parentProto.callInitHooks) { //先調(diào)用父類的新增函數(shù) parentProto.callInitHooks.call(this); } this._initHooksCalled = true; // 此init已被調(diào)用,標(biāo)志位置為true for (var i = 0, len = proto._initHooks.length; i < len; i++) { proto._initHooks[i].call(this); // 循環(huán)調(diào)用新增的初始化函數(shù) } };
執(zhí)行這段函數(shù)時(shí),先會(huì)遞歸的調(diào)用父類的callInitHooks函數(shù),之后循環(huán)調(diào)用已構(gòu)建好的
_initHooks數(shù)組中的初始函數(shù)。
首先看下示例程序:
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選項(xiàng),子類繼承其options并覆蓋了父類同名的options。
實(shí)現(xiàn)如下:
Class.extend = function (props) { var NewClass = function () { ... } ... if (proto.options) { props.options = extend(proto.options, props.options); } ... return NewClass; };
這個(gè)功能有了之前的基礎(chǔ)實(shí)現(xiàn)就相當(dāng)簡(jiǎn)單了。判斷父類是否有optios選項(xiàng),若有者將子類的optios進(jìn)行復(fù)制。
靜態(tài)屬性方法var MyClass = Class.extend({ statics: { FOO: "bar", BLA: 5 } }); MyClass.FOO; // "bar"
實(shí)現(xiàn)如下:
Class.extend = function (props) { var NewClass = function () { ... } ... if (props.statics) { extend(NewClass, props.statics); delete props.statics; } ... extend(proto, props); ... return NewClass; };
實(shí)現(xiàn)與內(nèi)置選項(xiàng)類似,需注意的是extend執(zhí)行之后得把props中的statics字段刪除,以免之后重復(fù)復(fù)制到原型上。
MixinsMixins 是一個(gè)在舊類上添加新的屬性、方法的技術(shù)。
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
實(shí)現(xiàn)與靜態(tài)屬性方法類似:
Class.extend = function (props) { var NewClass = function () { ... } ... if (props.includes) { extend.apply(null, [proto].concat(props.includes)); delete props.includes; } extend(proto, props); //將參數(shù)復(fù)制到NewClass的prototypez中 return NewClass; }; Class.include = function (props) { Util.extend(this.prototype, props); return this; };
也是同樣調(diào)用了extend函數(shù),將include復(fù)制到原型中。為什么使用apply方法,主要是為了支持include為數(shù)組的情況。
總結(jié)Leaflet中繼承功能已全部實(shí)現(xiàn)完成。實(shí)現(xiàn)思路與一些小技巧值得我們借鑒。
這是完整實(shí)現(xiàn)代碼。
文章首發(fā)于Whj"s Website。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/88585.html
摘要:除了以上介紹的幾種對(duì)象創(chuàng)建方式,此外還有寄生構(gòu)造函數(shù)模式穩(wěn)妥構(gòu)造函數(shù)模式。 showImg(https://segmentfault.com/img/remote/1460000018196128); 面向?qū)ο?是以 對(duì)象 為中心的編程思想,它的思維方式是構(gòu)造。 面向?qū)ο?編程的三大特點(diǎn):封裝、繼承、多態(tài): 封裝:屬性方法的抽象 繼承:一個(gè)類繼承(復(fù)制)另一個(gè)類的屬性/方法 多態(tài):方...
摘要:目錄導(dǎo)語理解對(duì)象和面向?qū)ο蟮某绦蛟O(shè)計(jì)創(chuàng)建對(duì)象的方式的繼承機(jī)制原型對(duì)象原型鏈與原型對(duì)象相關(guān)的方法小結(jié)導(dǎo)語前面的系列文章,基本把的核心知識(shí)點(diǎn)的基本語法標(biāo)準(zhǔn)庫(kù)等章節(jié)講解完本章開始進(jìn)入核心知識(shí)點(diǎn)的高級(jí)部分面向?qū)ο蟮某绦蛟O(shè)計(jì),這一部分的內(nèi)容將會(huì)對(duì)對(duì)象 目錄 導(dǎo)語 1.理解對(duì)象和面向?qū)ο蟮某绦蛟O(shè)計(jì) 2.創(chuàng)建對(duì)象的方式 3.JavaScript的繼承機(jī)制 3.1 原型對(duì)象 3.2 原型鏈 3.3 與...
摘要:是完全的面向?qū)ο笳Z言,它們通過類的形式組織函數(shù)和變量,使之不能脫離對(duì)象存在。而在基于原型的面向?qū)ο蠓绞街校瑢?duì)象則是依靠構(gòu)造器利用原型構(gòu)造出來的。 JavaScript 函數(shù)式腳本語言特性以及其看似隨意的編寫風(fēng)格,導(dǎo)致長(zhǎng)期以來人們對(duì)這一門語言的誤解,即認(rèn)為 JavaScript 不是一門面向?qū)ο蟮恼Z言,或者只是部分具備一些面向?qū)ο蟮奶卣鳌1疚膶⒒貧w面向?qū)ο蟊疽猓瑥膶?duì)語言感悟的角度闡述為什...
摘要:的面向?qū)ο笾饕藘蓧K創(chuàng)建對(duì)象繼承。構(gòu)造函數(shù)一般來說,我們可以這樣定義構(gòu)造函數(shù)構(gòu)造函數(shù)的函數(shù)名常大寫在這里,我們沒有顯示的創(chuàng)建對(duì)象,沒有語句,卻將屬性和方法賦值給了。 面向?qū)ο笫擒浖_發(fā)方法。面向?qū)ο蟮母拍詈蛻?yīng)用已超越了程序設(shè)計(jì)和軟件開發(fā),擴(kuò)展到如數(shù)據(jù)庫(kù)系統(tǒng)、交互式界面、應(yīng)用結(jié)構(gòu)、應(yīng)用平臺(tái)、分布式系統(tǒng)、網(wǎng)絡(luò)管理結(jié)構(gòu)、CAD技術(shù)、人工智能等領(lǐng)域。面向?qū)ο笫且环N對(duì)現(xiàn)實(shí)世界理解和抽象的方法...
摘要:對(duì)象詳解對(duì)象深度剖析,深度理解對(duì)象這算是醞釀很久的一篇文章了。用空構(gòu)造函數(shù)設(shè)置類名每個(gè)對(duì)象都共享相同屬性每個(gè)對(duì)象共享一個(gè)方法版本,省內(nèi)存。 js對(duì)象詳解(JavaScript對(duì)象深度剖析,深度理解js對(duì)象) 這算是醞釀很久的一篇文章了。 JavaScript作為一個(gè)基于對(duì)象(沒有類的概念)的語言,從入門到精通到放棄一直會(huì)被對(duì)象這個(gè)問題圍繞。 平時(shí)發(fā)的文章基本都是開發(fā)中遇到的問題和對(duì)...
閱讀 3056·2021-11-18 10:02
閱讀 3324·2021-11-02 14:48
閱讀 3387·2019-08-30 13:52
閱讀 547·2019-08-29 17:10
閱讀 2079·2019-08-29 12:53
閱讀 1400·2019-08-29 12:53
閱讀 1024·2019-08-29 12:25
閱讀 2162·2019-08-29 12:17