摘要:面向?qū)ο髮崿F(xiàn)代碼動物發(fā)聲汪汪喵喵調(diào)用代碼動物發(fā)聲喵喵動物發(fā)聲汪汪當要增加一種動物時,只需增加一個繼承,不會影響其他已有的動物邏輯。所以的繼承和的原型繼承,可謂殊途同歸。
傳統(tǒng)面向?qū)ο蟮睦^承和多態(tài)
我們知道C++/Java/C#等面向?qū)ο笳Z言,都原生地支持類的繼承。繼承的核心作用大抵是創(chuàng)建一個派生類,并使其復(fù)用基本類(即父類)的字段和/或方法。并且派生類可以重寫基本類的方法。這樣基本類和派生類相同簽名的方法在被調(diào)用時,就會有不同的行為表現(xiàn),即為多態(tài)的實質(zhì)。換句話說,多態(tài)是透過繼承和重寫實現(xiàn)的。
舉例:實現(xiàn)不同的動物叫聲不同。
過程式編程(Java代碼):
void animalSpeak(String animal) { if(animal == "Dog") { System.out.println("汪汪"); } else if(animal == "Cat") { System.out.println("喵喵"); } else { System.out.println("動物發(fā)聲"); } } //調(diào)用代碼 animalSpeak("Dog"); //汪汪 animalSpeak("Cat"); //喵喵 animalSpeak(""); //動物發(fā)聲
這里一個問題是,如果增加一種動物,就要在speak方法增加if分支,此方法逐漸變得臃腫難維護。偶爾因增加一種動物,會偶爾誤傷其他動物的邏輯,也未嘗可知。面向?qū)ο笫降膭討B(tài)應(yīng)運而生。
面向?qū)ο髮崿F(xiàn)(java代碼)
class Animal { void speak() { System.out.println("動物發(fā)聲:"); } } class Dog extends Animal { void speak() { super.speak(); System.out.println("汪汪"); } } class Cat extends Animal { void speak() { super.speak(); System.out.println("喵喵"); } } void animalSpeak(Animal animal) { animal.speak(); } //調(diào)用代碼 animalSpeak(new Cat()); //動物發(fā)聲: //喵喵 animalSpeak(new Dog()); //動物發(fā)聲: //汪汪
當要增加一種動物時,只需增加一個class繼承 Animal,不會影響其他已有的動物speak邏輯??煽闯觯嫦?qū)ο蠖鄳B(tài)編程的一個核心思想是便于擴展和維護
結(jié)語:面向?qū)ο缶幊桃岳^承產(chǎn)生派生類和重寫派生類的方法,實現(xiàn)多態(tài)編程。核心思想是便于擴展和維護代碼,也避免if-else
JavaScript繼承實現(xiàn)Java繼承是class的繼承,而JavaScript的繼承一般是通過原型(prototype)實現(xiàn)。prototype的本質(zhì)是一個Object實例,它是在同一類型的多個實例之間共享的,它里面包含的是需要共享的方法(也可以有字段)。
JavaScript版原型繼承的實現(xiàn):
function Animal() { } Animal.prototype.speak = function () { console.log("動物發(fā)聲:"); } function Dog(name) { this.name = name; } Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog; Dog.prototype.speak = function () { //通過原型鏈找‘基本類’原型里的同名方法 this.__proto__.__proto__.speak.call(this); console.log("汪汪, 我是", this.name); } function Cat(name) { this.name = name; } Cat.prototype = Object.create(Animal.prototype); Cat.prototype.constructor = Cat; Cat.prototype.speak = function () { //通過原型鏈找‘基本類’原型里的同名方法 this.__proto__.__proto__.speak.call(this); console.log("喵喵, 我是", this.name); } //調(diào)用代碼 function animalSpeak(animal) { animal.speak(); } animalSpeak(new Dog("大黃")) console.log() animalSpeak(new Cat("小喵")) //動物發(fā)聲: //汪汪, 我是 大黃 //動物發(fā)聲: //喵喵, 我是 小喵JavaScript原型剖析
傳統(tǒng)面向?qū)ο笳Z言的class繼承是為代碼(方法和字段)復(fù)用,而JavaScript的prototype是在同類型實例之間共享的對象,它包含共享的方法(也可有字段)。所以java的class繼承和javascript的原型繼承,可謂殊途同歸。為了方便理解js原型,提出兩個概念:原型的design-time和run-time
Design-time 原型可理解為我們(程序員)如何設(shè)計js類型的自上而下的繼承關(guān)系。以上例看出,design-time是通過prototype賦值實現(xiàn)。
// Dog類型繼承自Animal Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog;Run-time 原型和原型鏈
Run-time可理解為,原型設(shè)計好之后,要創(chuàng)建實例了, 并且還能向上查找其繼承自哪個原型
// 創(chuàng)建Dog類型的一個實例 var dog = new Dog("大黃"); // ***打起精神*** 這里到了關(guān)鍵的地方:如何查找dog創(chuàng)建自哪個類型: // 顯而易見, dog是由Dog創(chuàng)造出來的 dog.__proto__ // Dog { constructor: [Function: Dog], speak: [Function] } // 再往上查找原型鏈: // 看出來是繼承了Animal的原型 dog.__proto__.__proto__ //Animal { speak: [Function] } // 再往上查找原型鏈: // 看出來是繼承了Object的原型 dog.__proto__.__proto__.__proto__ // {} // 再往上查找原型鏈: // 到了原型鏈的頂端。 dog.__proto__.__proto__.__proto__.__proto__ // null
所以在調(diào)用實例的方法,它會在原型鏈上自下而上,直到找到該方法。如果到了原型鏈頂端還沒有找到,就拋錯了。
結(jié)語: design-time原型是通過prototype賦值,設(shè)計好自上而下的繼承關(guān)系; run-time時通過實例的__proto__,自下而上在原型鏈中查找需要的方法。
再論prototype 與__proto__如前文述prototype可看成design-time的概念,以此prototype創(chuàng)造出一個實例。
var dog = new Dog("大黃"); //實質(zhì)是: //new 是便利構(gòu)造方法 var dog = Object.create(Dog.prototype); dog.name = "大黃"
__proto__是屬于實例的,可反查實例出自哪個prototype。 所以dog.__proto__顯然等于Dog.prototype.
dog.__proto__ == Dog.prototype //true
而Dog.prototype創(chuàng)自于 Animal.prototype:
Dog.prototype = Object.create(Animal.prototype);
所以Dog.prototype的__proto__即為Animal.prototype
Dog.prototype.__proto__ == Animal.prototype //true dog.__proto__.__proto__ == Animal.prototype //true
這樣就實現(xiàn)了run-time原型鏈自下而上的查找
結(jié)束語原型繼承是JS老生常談的話題,也是很重要但不易深入理解的技術(shù)點。本文里提出了design-time原型設(shè)計和run-time原型鏈查找,希望有助于此技術(shù)點的理解。
后記上文是對自定義函數(shù)類型實例的原型分析,漏掉了對JS內(nèi)置類型的原型分析。大家知道JS內(nèi)置類型是有:
undefined null bool number string object Function Date Error symbol (ES6)JS基本(primitive)類型原型分析
基本類型有undefined, null, bool, number, string和symbol. 基本類型是以字面量賦值或函數(shù)式賦值的,而非通過new或Object.create(...)出來。
基本類型是沒有design-time的prototype。但undefined/null之外的基本練習(xí),還是有run-time的__proto__
// 常用基本類型的字面量賦值: var b = true; var n = 1; var s = "str"; // 常用基本類型的run-time __proto__ // 需要說明的是,基本類型本身是沒有任何方法和字段的。 // 例如undefined/null, 不能調(diào)用其任何方法和字段。 // 這里調(diào)用b.__proto__時,會臨時生成一個基本類型包裝類的實例, // 即生成var tmp = new Boolean(b)。這是個object實例,返回__proto__ b.__proto__ // [Boolean: false] n.__proto__ // [Number: 0] s.__proto__ // [String: ""] // 以上 *.__proto__ 打印出的是,字面量值出自哪個函數(shù), 所以亦可以函數(shù)方式賦值, 跟字面量完全等價。 // 基本類型的函數(shù)式賦值: // *注意*的是,這里僅是調(diào)用函數(shù),沒有new。若用了new, 就構(gòu)造出一個對象實例,而再非基本類型了 b = Boolean(true) n = Number(1) s = String("") sym = Symbol("IBM") // ES6 新增的symbol沒有字面量賦值方式 // 特殊的case是undefined和null, 只有字面量賦值(沒函數(shù)方式賦值) // null 和 undefined是沒有__proto__的。 // 也可以理解為,null處于任何原型鏈的最頂端,這是因為null是object類型(typeof null == "object")。undefined不是object類型。 var nn = null; var un = undefined;JS基本類型的對象實例的原型分析
如果通過new 構(gòu)造基本類型的對象實例,那么就是對象而非原生態(tài)基本類型了。
var b = new Boolean(true) var n = new Number(1) var s = new String("")
它們就遵循自定義函數(shù)類型對象的原型法則了。以上述Number n為例:
// Number的 design-time的prototype: Number.prototype //[Number: 0] typeof Number.prototype //"object" Number.prototype instanceof Object // true. 原型本身就是一對象實例 // n的run-time __proto__原型鏈 n.__proto__ //[Number: 0],n是由Number函數(shù)構(gòu)造產(chǎn)生的 // 可看出,n 繼承了object類型 n.__proto__.__proto__ // {} // n的原型鏈頂端也是null n.__proto__.__proto__.__proto__ // nullJS內(nèi)置object類型的原型分析
JS內(nèi)置的Date, Error, Function其本身就是function,就是說 typeof Date, typeof Error, typeof Function 都是 ‘function". 所以讀者可用本文分析自定義函數(shù)類型原型的方法,自行分析這三者的design-time的prototype, 以及run-time的__proto__原型鏈。
需要特殊指出的是,我們幾乎不會用new Function的方式去創(chuàng)建Function的實例,而是透過function關(guān)鍵字去定義函數(shù)。
// 一般是用function這個關(guān)鍵字,去定義函數(shù)。這和通過new Function構(gòu)造本質(zhì)是一樣的 function foo() { } // 通過run-time的 __proto__,其實可看出,foo就是Function這個類型的一個實例 foo.__proto__ //[Function] foo instanceof Foo // true // 所以foo也就繼承了Function的design-time prototype // 而理解這一點很重要。 add.__proto__ == Function.prototype // true // 函數(shù)實例本身也是object類型 foo.__proto__.__proto__ //{} foo instanceof Object // true{}和Object.create(null)的區(qū)別
以下定義是等價的
var obj = {} // 字面量 var obj = new Object() // Object函數(shù)構(gòu)造 var obj = Object.create(Object.prototype) // Object原型構(gòu)造
obj run-time的__proto__即Object.prototype, 故obj繼承了Object.prototype的共享方法,例如toString(), valueOf()
obj.__proto__ == Object.prototype //true obj.toString() //"[object Object]"
var obj2 = Object.create(null) obj2.__proto__ // undefined obj2.toString() //TypeError: obj2.toString is not a function
可以看出,obj2無run-time的__proto__,沒有繼承Object.prototype,故而就不能調(diào)用.toString()方法了
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/84016.html
摘要:引子獨家解析原型繼承已經(jīng)比較全面的分析了自定義函數(shù)類型,內(nèi)置基本類和內(nèi)置對象類型的的以及的原型鏈。鑒于函數(shù)是的一等公民,另辟新篇介紹函數(shù)的原型及其應(yīng)用。函數(shù)本身也是對象,它遵循獨家解析原型繼承所描述的自定義函數(shù)類型對象的原型法則。 引子 獨家解析Javascript原型繼承已經(jīng)比較全面的分析了自定義函數(shù)類型,JS內(nèi)置基本類(undefined, null, bool, number, ...
摘要:支持的類型的內(nèi)置數(shù)據(jù)類型羅列如下自定義自定義這三種類型的賦值是同類似的。這根不同,這因為是沒有包裝類新增的基本類型,只支持函數(shù)式賦值,不支持字面量和函數(shù)構(gòu)造。 JavaScript支持的類型 JS的內(nèi)置數(shù)據(jù)類型羅列如下: undefined null bool number string function object Function Date ...
摘要:前端日報點贊通道精選聽說你沒來騰訊前端求職直播課筆試篇淘寶漏洞修補記一次踩坑記錄中的對象精讀發(fā)布中文深入理解筆記塊級作用域綁定架構(gòu)經(jīng)驗分享深入理解筆記字符串和正則表達式架構(gòu)經(jīng)驗分享深入理解筆記導(dǎo)讀架構(gòu)經(jīng)驗分享第期種使用提升應(yīng) 2017-07-16 前端日報 GitHub點贊通道 精選 聽說你沒來 JSConf 2017騰訊前端求職直播課——筆試篇淘寶 flexible.js 漏洞修補:...
摘要:繼承前言作為一門輕量級的腳本語言在和的橫空出世之后將其推向的新的高度雖然中出現(xiàn)的新的生成對象的類語法格式但依然為的語法糖而我們依然有必要從的原生實現(xiàn)入手來了解它的繼承實現(xiàn)方式給出了更加簡潔的固定的類聲明方式有興趣的可以查看阮一峰的入門下面給 javascript繼承 前言 javascript作為一門輕量級的腳本語言在ES6和node.js的橫空出世之后將其推向的新的高度,雖然 ES6...
摘要:特點跟借用構(gòu)造函數(shù)模式一樣,每次創(chuàng)建對象都會創(chuàng)建一遍方法。缺點寄生組合式繼承使用時說明解決了組合繼承存在的問題特點只調(diào)用了一次構(gòu)造函數(shù),并且因此避免了在上面創(chuàng)建不必要的多余的屬性原型鏈還能保持不變還能夠正常使用和缺點參考資料 原型鏈繼承 //父類 function Person(name, age) { this.name = name; this.age = age; ...
閱讀 2487·2021-10-09 09:41
閱讀 3460·2021-09-26 09:46
閱讀 987·2021-09-03 10:34
閱讀 3270·2021-08-11 11:22
閱讀 3471·2019-08-30 14:12
閱讀 803·2019-08-26 11:34
閱讀 3416·2019-08-26 11:00
閱讀 1862·2019-08-26 10:26