成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

獨(dú)家解析Javascript原型繼承

verano / 3246人閱讀

摘要:面向?qū)ο髮?shí)現(xiàn)代碼動(dòng)物發(fā)聲汪汪喵喵調(diào)用代碼動(dòng)物發(fā)聲喵喵動(dòng)物發(fā)聲汪汪當(dāng)要增加一種動(dòng)物時(shí),只需增加一個(gè)繼承,不會(huì)影響其他已有的動(dòng)物邏輯。所以的繼承和的原型繼承,可謂殊途同歸。

傳統(tǒng)面向?qū)ο蟮睦^承和多態(tài)

我們知道C++/Java/C#等面向?qū)ο笳Z(yǔ)言,都原生地支持類(lèi)的繼承。繼承的核心作用大抵是創(chuàng)建一個(gè)派生類(lèi),并使其復(fù)用基本類(lèi)(即父類(lèi))的字段和/或方法。并且派生類(lèi)可以重寫(xiě)基本類(lèi)的方法。這樣基本類(lèi)和派生類(lèi)相同簽名的方法在被調(diào)用時(shí),就會(huì)有不同的行為表現(xiàn),即為多態(tài)的實(shí)質(zhì)。換句話(huà)說(shuō),多態(tài)是透過(guò)繼承重寫(xiě)實(shí)現(xiàn)的。

舉例:實(shí)現(xiàn)不同的動(dòng)物叫聲不同。
過(guò)程式編程(Java代碼):

void animalSpeak(String animal) {
    if(animal == "Dog") {
        System.out.println("汪汪");
    } else if(animal == "Cat") {
        System.out.println("喵喵");
    } else {
      System.out.println("動(dòng)物發(fā)聲");
    }
}

//調(diào)用代碼
animalSpeak("Dog"); //汪汪
animalSpeak("Cat"); //喵喵
animalSpeak("");    //動(dòng)物發(fā)聲

這里一個(gè)問(wèn)題是,如果增加一種動(dòng)物,就要在speak方法增加if分支,此方法逐漸變得臃腫難維護(hù)。偶爾因增加一種動(dòng)物,會(huì)偶爾誤傷其他動(dòng)物的邏輯,也未嘗可知。面向?qū)ο笫降膭?dòng)態(tài)應(yīng)運(yùn)而生。

面向?qū)ο髮?shí)現(xiàn)(java代碼)

class Animal {
    void speak() {
       System.out.println("動(dòng)物發(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());     //動(dòng)物發(fā)聲: 
                            //喵喵
animalSpeak(new Dog());     //動(dòng)物發(fā)聲: 
                            //汪汪

當(dāng)要增加一種動(dòng)物時(shí),只需增加一個(gè)class繼承 Animal,不會(huì)影響其他已有的動(dòng)物speak邏輯??煽闯?,面向?qū)ο蠖鄳B(tài)編程的一個(gè)核心思想是便于擴(kuò)展維護(hù)

結(jié)語(yǔ):面向?qū)ο缶幊桃岳^承產(chǎn)生派生類(lèi)和重寫(xiě)派生類(lèi)的方法,實(shí)現(xiàn)多態(tài)編程。核心思想是便于擴(kuò)展和維護(hù)代碼,也避免if-else

JavaScript繼承實(shí)現(xiàn)

Java繼承是class的繼承,而JavaScript的繼承一般是通過(guò)原型(prototype)實(shí)現(xiàn)。prototype的本質(zhì)是一個(gè)Object實(shí)例,它是在同一類(lèi)型的多個(gè)實(shí)例之間共享的,它里面包含的是需要共享的方法(也可以有字段)。
JavaScript版原型繼承的實(shí)現(xiàn):

function Animal() {
}
Animal.prototype.speak = function () {
    console.log("動(dòng)物發(fā)聲:");
}

function Dog(name) {
    this.name = name;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function () {
    //通過(guò)原型鏈找‘基本類(lèi)’原型里的同名方法
    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 () {
    //通過(guò)原型鏈找‘基本類(lèi)’原型里的同名方法
    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("小喵"))

//動(dòng)物發(fā)聲:
//汪汪, 我是 大黃

//動(dòng)物發(fā)聲:
//喵喵, 我是 小喵
JavaScript原型剖析

傳統(tǒng)面向?qū)ο笳Z(yǔ)言的class繼承是為代碼(方法和字段)復(fù)用,而JavaScript的prototype是在同類(lèi)型實(shí)例之間共享的對(duì)象,它包含共享的方法(也可有字段)。所以java的class繼承和javascript的原型繼承,可謂殊途同歸。為了方便理解js原型,提出兩個(gè)概念:原型的design-time和run-time

Design-time 原型

可理解為我們(程序員)如何設(shè)計(jì)js類(lèi)型的自上而下的繼承關(guān)系。以上例看出,design-time是通過(guò)prototype賦值實(shí)現(xiàn)。

// Dog類(lèi)型繼承自Animal
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Run-time 原型和原型鏈

Run-time可理解為,原型設(shè)計(jì)好之后,要?jiǎng)?chuàng)建實(shí)例了, 并且還能向上查找其繼承自哪個(gè)原型

// 創(chuàng)建Dog類(lèi)型的一個(gè)實(shí)例
var dog = new Dog("大黃");

// ***打起精神*** 這里到了關(guān)鍵的地方:如何查找dog創(chuàng)建自哪個(gè)類(lèi)型:
// 顯而易見(jiàn), dog是由Dog創(chuàng)造出來(lái)的
dog.__proto__ // Dog { constructor: [Function: Dog], speak: [Function] }

// 再往上查找原型鏈:
// 看出來(lái)是繼承了Animal的原型
dog.__proto__.__proto__  //Animal { speak: [Function] }

// 再往上查找原型鏈:
// 看出來(lái)是繼承了Object的原型
dog.__proto__.__proto__.__proto__ // {}

// 再往上查找原型鏈:
// 到了原型鏈的頂端。
dog.__proto__.__proto__.__proto__.__proto__ // null

所以在調(diào)用實(shí)例的方法,它會(huì)在原型鏈上自下而上,直到找到該方法。如果到了原型鏈頂端還沒(méi)有找到,就拋錯(cuò)了。

結(jié)語(yǔ): design-time原型是通過(guò)prototype賦值,設(shè)計(jì)好自上而下的繼承關(guān)系; run-time時(shí)通過(guò)實(shí)例的__proto__,自下而上在原型鏈中查找需要的方法。

再論prototype 與__proto__

如前文述prototype可看成design-time的概念,以此prototype創(chuàng)造出一個(gè)實(shí)例。

var dog = new Dog("大黃");
//實(shí)質(zhì)是:
//new 是便利構(gòu)造方法
var dog = Object.create(Dog.prototype);
dog.name = "大黃"

__proto__是屬于實(shí)例的,可反查實(shí)例出自哪個(gè)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

這樣就實(shí)現(xiàn)了run-time原型鏈自下而上的查找

結(jié)束語(yǔ)

原型繼承是JS老生常談的話(huà)題,也是很重要但不易深入理解的技術(shù)點(diǎn)。本文里提出了design-time原型設(shè)計(jì)和run-time原型鏈查找,希望有助于此技術(shù)點(diǎn)的理解。

后記

上文是對(duì)自定義函數(shù)類(lèi)型實(shí)例的原型分析,漏掉了對(duì)JS內(nèi)置類(lèi)型的原型分析。大家知道JS內(nèi)置類(lèi)型是有:

undefined
null
bool
number
string
object
    Function
    Date
    Error
 symbol (ES6)
JS基本(primitive)類(lèi)型原型分析

基本類(lèi)型有undefined, null, bool, number, string和symbol. 基本類(lèi)型是以字面量賦值函數(shù)式賦值的,而非通過(guò)new或Object.create(...)出來(lái)。
基本類(lèi)型是沒(méi)有design-time的prototype。但undefined/null之外的基本練習(xí),還是有run-time的__proto__

// 常用基本類(lèi)型的字面量賦值:
var b = true;
var n = 1;
var s = "str";

// 常用基本類(lèi)型的run-time __proto__
// 需要說(shuō)明的是,基本類(lèi)型本身是沒(méi)有任何方法和字段的。
// 例如undefined/null, 不能調(diào)用其任何方法和字段。
// 這里調(diào)用b.__proto__時(shí),會(huì)臨時(shí)生成一個(gè)基本類(lèi)型包裝類(lèi)的實(shí)例,
// 即生成var tmp = new Boolean(b)。這是個(gè)object實(shí)例,返回__proto__
b.__proto__ // [Boolean: false]
n.__proto__ // [Number: 0]
s.__proto__ // [String: ""]
// 以上 *.__proto__ 打印出的是,字面量值出自哪個(gè)函數(shù), 所以亦可以函數(shù)方式賦值, 跟字面量完全等價(jià)。

// 基本類(lèi)型的函數(shù)式賦值:
// *注意*的是,這里僅是調(diào)用函數(shù),沒(méi)有new。若用了new, 就構(gòu)造出一個(gè)對(duì)象實(shí)例,而再非基本類(lèi)型了
b = Boolean(true)
n = Number(1)
s = String("")
sym = Symbol("IBM") // ES6 新增的symbol沒(méi)有字面量賦值方式

// 特殊的case是undefined和null, 只有字面量賦值(沒(méi)函數(shù)方式賦值)
// null 和 undefined是沒(méi)有__proto__的。
// 也可以理解為,null處于任何原型鏈的最頂端,這是因?yàn)閚ull是object類(lèi)型(typeof null == "object")。undefined不是object類(lèi)型。
var nn = null;
var un = undefined;
JS基本類(lèi)型的對(duì)象實(shí)例的原型分析

如果通過(guò)new 構(gòu)造基本類(lèi)型的對(duì)象實(shí)例,那么就是對(duì)象而非原生態(tài)基本類(lèi)型了。

var b = new Boolean(true)
var n = new Number(1)
var s = new String("")

它們就遵循自定義函數(shù)類(lèi)型對(duì)象的原型法則了。以上述Number n為例:

// Number的 design-time的prototype:
Number.prototype //[Number: 0]
typeof Number.prototype //"object"
Number.prototype instanceof Object // true. 原型本身就是一對(duì)象實(shí)例

// n的run-time __proto__原型鏈
n.__proto__ //[Number: 0],n是由Number函數(shù)構(gòu)造產(chǎn)生的
// 可看出,n 繼承了object類(lèi)型
n.__proto__.__proto__ // {}
// n的原型鏈頂端也是null
n.__proto__.__proto__.__proto__ // null
JS內(nèi)置object類(lèi)型的原型分析

JS內(nèi)置的Date, Error, Function其本身就是function,就是說(shuō) typeof Date, typeof Error, typeof Function 都是 ‘function". 所以讀者可用本文分析自定義函數(shù)類(lèi)型原型的方法,自行分析這三者的design-time的prototype, 以及run-time的__proto__原型鏈。

需要特殊指出的是,我們幾乎不會(huì)用new Function的方式去創(chuàng)建Function的實(shí)例,而是透過(guò)function關(guān)鍵字去定義函數(shù)。

// 一般是用function這個(gè)關(guān)鍵字,去定義函數(shù)。這和通過(guò)new Function構(gòu)造本質(zhì)是一樣的
function foo() { }

// 通過(guò)run-time的 __proto__,其實(shí)可看出,foo就是Function這個(gè)類(lèi)型的一個(gè)實(shí)例
foo.__proto__ //[Function]
foo instanceof Foo // true

// 所以foo也就繼承了Function的design-time prototype
// 而理解這一點(diǎn)很重要。
add.__proto__ == Function.prototype // true

// 函數(shù)實(shí)例本身也是object類(lèi)型
foo.__proto__.__proto__ //{}
foo instanceof Object // true
{}和Object.create(null)的區(qū)別

以下定義是等價(jià)的

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無(wú)run-time的__proto__,沒(méi)有繼承Object.prototype,故而就不能調(diào)用.toString()方法了

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/84016.html

相關(guān)文章

  • 獨(dú)家解析Javascript原型繼承 - 之函數(shù)原型和AOP

    摘要:引子獨(dú)家解析原型繼承已經(jīng)比較全面的分析了自定義函數(shù)類(lèi)型,內(nèi)置基本類(lèi)和內(nèi)置對(duì)象類(lèi)型的的以及的原型鏈。鑒于函數(shù)是的一等公民,另辟新篇介紹函數(shù)的原型及其應(yīng)用。函數(shù)本身也是對(duì)象,它遵循獨(dú)家解析原型繼承所描述的自定義函數(shù)類(lèi)型對(duì)象的原型法則。 引子 獨(dú)家解析Javascript原型繼承已經(jīng)比較全面的分析了自定義函數(shù)類(lèi)型,JS內(nèi)置基本類(lèi)(undefined, null, bool, number, ...

    ispring 評(píng)論0 收藏0
  • 閑話(huà)JavaScript數(shù)據(jù)類(lèi)型

    摘要:支持的類(lèi)型的內(nèi)置數(shù)據(jù)類(lèi)型羅列如下自定義自定義這三種類(lèi)型的賦值是同類(lèi)似的。這根不同,這因?yàn)槭菦](méi)有包裝類(lèi)新增的基本類(lèi)型,只支持函數(shù)式賦值,不支持字面量和函數(shù)構(gòu)造。 JavaScript支持的類(lèi)型 JS的內(nèi)置數(shù)據(jù)類(lèi)型羅列如下: undefined null bool number string function object Function Date ...

    jerryloveemily 評(píng)論0 收藏0
  • 2017-07-16 前端日?qǐng)?bào)

    摘要:前端日?qǐng)?bào)點(diǎn)贊通道精選聽(tīng)說(shuō)你沒(méi)來(lái)騰訊前端求職直播課筆試篇淘寶漏洞修補(bǔ)記一次踩坑記錄中的對(duì)象精讀發(fā)布中文深入理解筆記塊級(jí)作用域綁定架構(gòu)經(jīng)驗(yàn)分享深入理解筆記字符串和正則表達(dá)式架構(gòu)經(jīng)驗(yàn)分享深入理解筆記導(dǎo)讀架構(gòu)經(jīng)驗(yàn)分享第期種使用提升應(yīng) 2017-07-16 前端日?qǐng)?bào) GitHub點(diǎn)贊通道 精選 聽(tīng)說(shuō)你沒(méi)來(lái) JSConf 2017騰訊前端求職直播課——筆試篇淘寶 flexible.js 漏洞修補(bǔ):...

    yeyan1996 評(píng)論0 收藏0
  • javascript繼承 --- 多種繼承方式解析(ES5)

    摘要:繼承前言作為一門(mén)輕量級(jí)的腳本語(yǔ)言在和的橫空出世之后將其推向的新的高度雖然中出現(xiàn)的新的生成對(duì)象的類(lèi)語(yǔ)法格式但依然為的語(yǔ)法糖而我們依然有必要從的原生實(shí)現(xiàn)入手來(lái)了解它的繼承實(shí)現(xiàn)方式給出了更加簡(jiǎn)潔的固定的類(lèi)聲明方式有興趣的可以查看阮一峰的入門(mén)下面給 javascript繼承 前言 javascript作為一門(mén)輕量級(jí)的腳本語(yǔ)言在ES6和node.js的橫空出世之后將其推向的新的高度,雖然 ES6...

    yankeys 評(píng)論0 收藏0
  • JavaScript常用6大繼承方式解析

    摘要:特點(diǎn)跟借用構(gòu)造函數(shù)模式一樣,每次創(chuàng)建對(duì)象都會(huì)創(chuàng)建一遍方法。缺點(diǎn)寄生組合式繼承使用時(shí)說(shuō)明解決了組合繼承存在的問(wèn)題特點(diǎn)只調(diào)用了一次構(gòu)造函數(shù),并且因此避免了在上面創(chuàng)建不必要的多余的屬性原型鏈還能保持不變還能夠正常使用和缺點(diǎn)參考資料 原型鏈繼承 //父類(lèi) function Person(name, age) { this.name = name; this.age = age; ...

    yearsj 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<