JavaScript面向?qū)ο笈c繼承(2)

面向?qū)ο笾蓄愔傅氖峭活愋蛯?duì)象的抽象,首字母大寫,比如前篇中的形狀Shape 類,三角形是通過(guò)Shape擴(kuò)展而來(lái),則也是一個(gè)類,Shape稱之為它的父類,它是Shape的子類,同理Rect也是Shape的一個(gè)子類。類的具體抽象稱之為實(shí)例,通常為小寫,創(chuàng)建實(shí)例的過(guò)程稱之為實(shí)例化。上文中triangle就是一個(gè)Triangle三角形的實(shí)例,指具體畫出的那個(gè)三角形。關(guān)于父類,子類的實(shí)例

  • 父類 Animal
  • 子類 Cat 實(shí)例 cat1_tom
  • 子類 Dog 實(shí)例 dog1
    Animal 指所有動(dòng)物,Cat 指所有貓 繼承Animal 是動(dòng)物的一個(gè)子類,cat1_tom 指的具體一個(gè)叫 tom 的貓。有了類我們就需要給類加一些標(biāo)識(shí),以區(qū)分類之間的區(qū)別、即屬性和方法。

1. JS原型

弄清楚了類是什么,而JavaScript沒(méi)有類的概念,是通過(guò)原型來(lái)實(shí)現(xiàn)面向?qū)ο?。在以類為中心的面向?qū)ο缶幊陶Z(yǔ)言中,類和對(duì)象的關(guān)系可以想象成鑄模和鑄件的關(guān)系,對(duì)象總是從類中創(chuàng)建而來(lái)。而在原型編程的思想中,類并不是必需的,對(duì)象未必需要從類中創(chuàng)建而來(lái),一個(gè)對(duì)象是通過(guò)克隆另外一個(gè)對(duì)象所得到的。

ES6為了在繼承上與傳統(tǒng)面向?qū)ο笳Z(yǔ)言更加類似,引入了class。
ES5 的繼承,實(shí)質(zhì)是先創(chuàng)造子類的實(shí)例對(duì)象 this,然后再將父類的方法添加到 this 上面(Parent.apply(this))。ES6 的繼承機(jī)制完全不同,實(shí)質(zhì)是先將父類實(shí)例對(duì)象的屬性和方法,加到 this 上面(所以在 constructor 里必須先調(diào)用super方法),然后再用子類的構(gòu)造函數(shù)修改this。

從設(shè)計(jì)模式的角度講,原型模式是用于創(chuàng)建對(duì)象的一種模式,如果我們想要?jiǎng)?chuàng)建一個(gè)對(duì)象,一種方法是先指定它的類型,然后通過(guò)類來(lái)創(chuàng)建這個(gè)對(duì)象。原型模式選擇了另外一種方式,我們不再關(guān)心對(duì)象的具體類型,而是找到一個(gè)對(duì)象,然后通過(guò)克隆來(lái)創(chuàng)建一個(gè)一模一樣的對(duì)象。而克隆出來(lái)的這個(gè)對(duì)象會(huì)記住他的原型,由誰(shuí)克隆而來(lái),同時(shí)也會(huì)共享原型的屬性和方法。這樣一個(gè)一個(gè)對(duì)象克隆而來(lái),則形成了一條原型鏈。對(duì)上文中的例子而言,三角形的原型是形狀,貓和狗的原型是動(dòng)物。

2. 構(gòu)造函數(shù)
在js中類之后跟的是一個(gè)構(gòu)造函數(shù)。

function Shape(name) {
  this.val = 1;
  this.name = name;
  this.all = '圖形';
  return this.name
}
let a = Shape('a'); // 'a'

let shape1 = new Shape('triangle'); 
let shape2 = new Shape('rect');

構(gòu)造函數(shù)的定義與一般函數(shù)的定義相同,注意首字母大寫。構(gòu)造函數(shù)本質(zhì)上還是一個(gè)函數(shù),可以傳參可以有返回值,只是內(nèi)部使用了this變量,函數(shù)存在調(diào)用問(wèn)題:

直接調(diào)用:在瀏覽器環(huán)境中相當(dāng)于在window上掛在了val這個(gè)屬性,值為1。請(qǐng)注意這個(gè)特點(diǎn),如果Shape.call(obj) 即相當(dāng)于設(shè)定obj對(duì)象的val為1。
new 調(diào)用:生成一個(gè)實(shí)例,即生成一個(gè)新對(duì)象,這個(gè)this指向當(dāng)前新生成的對(duì)象。

constructor和prototype
實(shí)例/構(gòu)造函數(shù)(構(gòu)造器)的關(guān)系是
A為B的構(gòu)造函數(shù) 則 B為A的一個(gè)實(shí)例。

首先創(chuàng)建一個(gè)Cat的構(gòu)造函數(shù),希望say是Cat的實(shí)例共享屬性,

function Cat(name) {
  this.name = name;
  this.say = function() {console.log(this.name)};
}

let cat1 = new Cat('tom'); 
let cat2 = new Cat('bob');
cat1.say === cat2.say // false

但是發(fā)現(xiàn)cat1 cat2的共有方法all并沒(méi)有共享,每一個(gè)實(shí)例對(duì)象,都有自己的屬性和方法的副本。這不僅無(wú)法做到數(shù)據(jù)共享,也是極大的資源浪費(fèi), 那么引入prototype對(duì)象:

function Cat(name) {
  this.name = name;
}
Cat.prototype.say = function() {
  console.log(this.name);
}
let cat1 = new Cat('tom'); 
let cat2 = new Cat('bob');
cat1.say === cat2.say 
cat1.say === Cat.prototype.say; // true
cat1.prototype; // undefined
cat1.hasOwnProperty('say');// false

實(shí)例對(duì)象的constructor屬性指向其構(gòu)造函數(shù)(1),這樣看起來(lái)實(shí)例對(duì)象好像“繼承”了prototype對(duì)象一樣。實(shí)例沒(méi)有prototype,上文最后一行代碼通過(guò)hasOwnPropertyk可以判斷say這個(gè)方法并不是cat1自己的方法,如果一個(gè)方法沒(méi)有在實(shí)例對(duì)象自身找到,則向其構(gòu)造函數(shù)prototype中開始尋找(2)。

既然實(shí)例是繼承自構(gòu)造器的prototype,那么有沒(méi)有一個(gè)屬性可以直接表示對(duì)象的繼承關(guān)系呢?答案是有的proto,很多瀏覽器都實(shí)現(xiàn)了這個(gè)屬性,如下所示。

cat1.__proto__ === Cat.prototype // true
Cat.__proto__ === Function.prototype; // true
Function.prototype.__proto__ === Object.prototype; // true

從上我們可以發(fā)現(xiàn) Cat 構(gòu)造器的原型為Function.prototype ,Cat.prototype的原型為Object.prototype,所以當(dāng)cat1調(diào)toString時(shí) Cat.prototype上沒(méi)有找到 就去Function.prototype上尋找,這就構(gòu)成了原型鏈。但是對(duì)象的原型鏈查找和構(gòu)造函數(shù)的原型查找又有一點(diǎn)小區(qū)別(不查Function),構(gòu)造器生成的實(shí)例對(duì)象原型鏈的查找過(guò)程可以如下表示:

cat1 
 => cat1.__proto__(Cat.prototype) 
 => cat1.__proto__.__proto__(Function.prototype) 
 => cat1.__proto__.__proto__.__proto__ (Object.prototype)

還有通過(guò)對(duì)象字面量創(chuàng)建的對(duì)象的原型鏈查找方式

let obj = {};
obj => obj.__proto__(Object.prototype) ;

這里根據(jù)上文加粗(2)的語(yǔ)言可以得到Function.prototype 的構(gòu)造函數(shù)是Object(3)。

都有constructor
上文的兩個(gè)實(shí)例對(duì)象cat1 cat2,他們都具有一個(gè)屬性constructor,指向?qū)嵗臉?gòu)建函數(shù)Cat,意思是他們由Cat創(chuàng)建而來(lái)。實(shí)例有一個(gè)constructor屬性,指向其構(gòu)造函數(shù)(4)

cat1.constructor === Cat; // true
cat1.constructor === Cat; // true
Cat.constructor === Function; // true
Cat.prototype.constructor === Cat; // true

Object.constructor === Function;// true

構(gòu)造函數(shù)同樣具有construtor,指向Function,Cat.prototype同樣具有construtor,指向他自身,構(gòu)造函數(shù)的prototype對(duì)象的constructor指向該構(gòu)造函數(shù)(5)。

根據(jù)上文最后一行代碼 可以判斷Object 的構(gòu)造函數(shù) 是Function。則我們可以得到Object是Function的一個(gè)實(shí)例。如下Object 與 Function的關(guān)系是

??Object是Function的一個(gè)實(shí)例。
??Function.prototype 是 Object 的 一個(gè)實(shí)例。

根據(jù)上文總結(jié)如下:

??實(shí)例對(duì)象的constructor指向其構(gòu)造器。
??實(shí)例對(duì)象沒(méi)有prototype。
??實(shí)例對(duì)象可以通過(guò)構(gòu)造函數(shù)的prototype對(duì)象實(shí)現(xiàn)屬性方法共享。’
??實(shí)例對(duì)象的proto 原型指向其構(gòu)造函數(shù)的prototype對(duì)象
構(gòu)造器的constructor指向 Function。
構(gòu)造函數(shù)的prototype可以掛在公共屬性方法,prototype的constructor屬性指向該構(gòu)造函數(shù)。
構(gòu)造函數(shù) 的proto 原型指向 Function.prototype。
構(gòu)造函數(shù)prototype對(duì)象的 proto 原型指向Object.prototype。
對(duì)象原型指的是對(duì)象的 proto 屬性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容