1. 程式人生 > >對Javascript 類、原型鏈、繼承的理解

對Javascript 類、原型鏈、繼承的理解

自己 test 參數 自動 3.3 圖片 返回值 面向 指向

一、序言

??和其他面向對象的語言(如Java)不同,Javascript語言對類的實現和繼承的實現沒有標準的定義,而是將這些交給了程序員,讓程序員更加靈活地(當然剛開始也更加頭疼)去定義類,實現繼承。(以下不討論ES6中利用class、extends關鍵字來實現類和繼承;實質上,ES6中的class、extends關鍵字是利用語法糖實現的)

Javascript靈活到甚至可以實現接口的封裝(類似Java中的Interface和implements)。

二、類的實現

1.我對類的理解

??首先,我先說說我對類的理解:類是包含了一系列【屬性/方法】的集合,可以通過類的構造函數創建一個實例對象(例如人類是一個類,而每一個人就是一個實例對象),而這個實例對象中會包含兩方面內容:

?a.類的所有非靜態【屬性/方法】



???非靜態【屬性/方法】就是每一個實例所特有的,屬於個性。(例如每個人的名字都不相同,而名字這個屬性就是一個非靜態屬性)

?b.類的所有靜態【屬性/方法】

???靜態【屬性/方法】就是每一個實例所共享的,屬於共性。(例如每個人都要吃飯,而吃飯這個方法就是一個非靜態方法)

2.Javascript對類的實現

?a.利用函數創建類,利用new關鍵字生成實例對象

?(話不多說,先上代碼,以下沒有特別說明的話,我都會先上代碼,然後進行解釋說明)

// 代碼2.2.a
function Human() {
    console.log(‘create human here‘)
}
var fakeperson = Human() // undefined
var person = new Human() // {}

這裏Human既是一個普通函數,也是一個類的構造函數,當調用Human()的時候,它作為一個普通函數會被執行,會輸出create human here,但是沒有返回值(即返回undefined);而當調用new Human()時,也會輸出create human here並且返回一個對象。因為我們用Human這個函數來構造對象,所以我們也把Human稱作構造函數。所以通過定義構造函數,就相當於定義了一個類,通過new關鍵字,即可生成一個實例化的對象。


?b.利用構造函數實現非靜態【屬性/方法】

// 代碼2.2.b
function Human(name) {
    this.name = name
}
var person_1 = new Human(‘Jack‘)
var person_2 = new Human(‘Rose‘)
console.log(person_1.name)  // Jack
console.log(person_2.name)  // Rose

這裏的Human構造函數中多了一個參數並且函數體中多了一句this.name = name,這句話的中的this指針指向new關鍵字返回的實例化對象,所以根據構造函數參數的不同,其生成的對象中的具有的屬性name的值也會不同。而這裏的name就是這個類的** 非靜態【屬性/方法】**


?c.利用prototype實現靜態【屬性/方法】

這裏因為要用到原型鏈的知識,所以放到原型鏈後面說。

三、原型鏈


?1.類的prototype是什麽?

???在Javascript中,每當我們定義一個構造函數,Javascript引擎就會自動為這個類中添加一個prototype(也被稱作原型)

?**2.對象的__proto__是什麽?**

???在Javascript中,每當我們使用new創建一個對象時,Javascript引擎就會自動為這個對象中添加一個__proto__屬性,並讓其指向其類的prototype

// 代碼3.2
function Human(name) {
    this.name = name
}
console.log(Human.prototype)
var person_test1 = new Human(‘Test1‘)
var person_test2 = new Human(‘Test2‘)
console.log(person_test1.__proto__)
console.log(person_test2.__proto__)
console.log(Human.prototype === person_test1.__proto__) // true
console.log(Human.prototype === person_test2.__proto__) // true

我們會發現Human.prototype是一個對象,Human類的實例化對象person_test1、person_test2下都有一個屬性__proto__也是對象,並且它們都等於Human.prototype,我們知道在Javascript中引用類型的相等意味著他們所指向的是同一個對象。所以我們可以得到結論,任何一個實例化對象的__proto__屬性都指向其類的prototype。

?**3.對象的__proto__有什麽作用?**

// 代碼3.3
var Pproto = {
    name:‘jack‘
}
var person = {
    __proto__:Pproto
}
console.log(person.name) // jack
person.name = ‘joker‘
console.log(person.name) // joker

我們發現最開始我們並沒有給person定義name屬性,為什麽console出來jack呢?這就是Javascript著名的原型鏈的結果啦。話不多說,先上圖:
技術分享圖片

當我們訪問person.name時,發生了什麽呢?
首先它會訪問person對象本身的屬性,如果本身沒有定義name屬性的話,它會去尋找它的__proto__屬性對象,在這個例子中person的__proto__屬性對應的是Pproto對象,所以person的__proto__指向了Pproto,然後我們發現Pproto對象是具有name屬性的,那麽person.name就到此為止,返回了jack,但是如果我們又給person加上了一個自身的屬性name呢?這時,再次person.name就不會再尋找__proto__了,因為person本身已經具有了name屬性,而且其值為joker,所以這裏會返回joker.


我們註意到上圖中Pproto的__proto__指向了Object,這是因為每一個通過字面量的方式創建出來的對象它們都默認是Object類的對象,所以它們的__proto__自然指向Object.prototype。


?4.利用prototype實現靜態【屬性/方法】

// 代碼3.4
function Human(name) {
    this.name = name
}
Human.prototype.eat = function () {
    console.log(‘I eat!‘)
}
var person_1 = new Human(‘Jack‘)
var person_2 = new Human(‘Rose‘)
person_1.eat() // I eat!
person_2.eat() // I eat!
console.log(person_1.eat === person_2.eat) // true

這裏我們在構造函數外多寫了一句:Human.prototype.eat = function() {...} 這樣以後每個通過Human實例化的對象的__proto__都會指向Human.prototype,並且根據上述原型鏈知識,我們可以知道只要構造函數中沒有定義同名的非靜態【屬性/方法】,那麽每個對象訪問say方法時,訪問的其實都是Human.prototype.say方法,這樣我們就利用prototype實現了類的靜態【屬性/方法】,所有的對象實現了共有的特性,那就是eat

四、繼承的實現

1.我對繼承的理解

??假如有n(n>=2)個類,他們的一些【屬性/方法】不一樣,但是也有一些【屬性/方法】是相同的,所以我們每次定義它們的時候都要重復的去定義這些相同的【屬性/方法】,那樣豈不是很煩?所以一些牛逼的程序員想到,能不能像兒子繼承父親的基因一樣,讓這些類也像“兒子們”一樣去“繼承”他們的“父親”(而這裏的父親就是包含他們所具有的相同的【屬性/方法】)。這樣我們就可以多定義一個類,把它叫做父類,在它的裏面包含所有的這些子類所具有的相同的【屬性/方法】,然後通過繼承的方式,讓所有的子類都可以訪問這些【屬性/方法】,而不用每次都在子類的定義中去定義這些【屬性/方法】了。

2.原型鏈實現繼承(讓子類繼承了父類的靜態【屬性/方法】)

??

// 代碼4.1
function Father() {
}
Father.prototype.say = function() {
    console.log(‘I am talking...‘)
}
function Son() {
}
var sonObj_1 = new Son()
console.log(sonObj_1.say) // undefined

// 原型鏈實現繼承的關鍵代碼
Son.prototype = new Father()

var sonObj_2 = new Son()
console.log(sonObj_2.say) // function() {...}

看到這句Son.prototype = new Father()你可能有點蒙圈,沒關系,我先上個原型鏈的圖,你分分鐘就能明白了
技術分享圖片

對著圖我們想一想,首先,一開始Son、Father兩個類沒有什麽關系,所以在訪問say的時候肯定是undefined,但是當我們使用了Son.prototype = new Father()後,我們知道通過new Son()生成的對象都會有__proto__屬性,而這個屬性指向Son.prototype,而這裏我們又讓它等於了一個Father的對象,而Father類又定義了靜態方法say,所以這裏我們的sonObj_2通過沿著原型鏈尋找,尋找到了say方法,於是就可以訪問到Father類的靜態方法say了。這樣就實現了子類繼承了父類的靜態【屬性/方法】,那麽如何讓子類繼承父類的非靜態【屬性/方法】呢?

3.構造函數實現繼承(讓子類繼承了父類的非靜態【屬性/方法】)

// 代碼4.3
function Father(name) {
    this.name = name
}
function Son() {
    Father.apply(this, arguments)
    this.sing = function() {
        console.log(this.name + ‘ is singing...‘)
    }
}
var sonObj_1 = new Son(‘jack‘)
var sonObj_2 = new Son(‘rose‘)
sonObj_1.sing() // jack is singing...
sonObj_2.sing() // rose is singing...

在這個例子中,通過在Son的構造函數中利用apply函數,執行了Father的構造函數,所以每一個Son對象實例化的過程中都會執行Father的構造函數,從而得到name屬性,這樣,每一個Son實例化的Son對象都會有不同的name屬性值,於是就實現了子類繼承了父類的非靜態【屬性/方法】

4.組合方式實現繼承(組合 原型鏈繼承 + 構造函數繼承)

??大家可以先自己思考思考哦,待續。。。

5.寄生組合方式實現繼承

??大家可以先自己思考思考哦,待續。。。

對Javascript 類、原型鏈、繼承的理解