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

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

出處:https://www.cnblogs.com/codernie/p/9098184.html

一、序言

  和其他面向物件的語言(如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關鍵字,即可生成一個例項化的物件。

(所以說,js中的函式也是一種物件。)

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,並且根據上述原型鏈知識,我們可以知道只要建構函式中沒有定義同名的非靜態【屬性/方法】,那麼每個物件訪問eat方法時,訪問的其實都是Human.prototype.eat方法,這樣我們就利用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.組合方式實現繼承(組合 原型鏈繼承 + 建構函式繼承)
顧名思義,就是結合上述兩種方法,然後同時實現對父類的靜態及非靜態【屬性/方法】的繼承,程式碼如下:

// 程式碼4.4
function Father(name) {
    this.name = name
}
Father.prototype.sayName = function() {
    console.log('My name is ' + this.name)
}
function Son() {
    Father.apply(this, arguments)
}
Son.prototype = new Father()
var sonObj_1 = new Son('jack')
var sonObj_2 = new Son('rose')
sonObj_1.sayName() // My name is jack
sonObj_2.sayName() // My name is rose

這裡子類Son沒有一個自己的方法,它的sayName方法繼承自父類的靜態方法sayName,建構函式中繼承了父類的建構函式方法,所以得到了非靜態的name屬性,因此它的例項物件都可以呼叫靜態方法sayName,但是因為它們各自的name不同,所以打印出來的name的值也不同。看到這裡,大家可能認為這已經是一種完美無缺的Javascript的繼承方式了,但是還差一丟丟,,因為原型鏈繼承不是一種純粹的繼承原型的方式,它有副作用,為什麼呢?因為在我們呼叫Son.prototype = new Father()的時候,不僅僅使Son的原型指向了一個Father的例項物件,而且還讓Father的建構函式執行了一遍,這樣就會執行this.name = name;所以這個Father物件就不純粹了,它具有了name屬性,並且值為father,那為什麼之後我們訪問的時候訪問不到這個值呢?這又是因為原型鏈的原因啦,話不多說先上圖:

所以這裡父類的建構函式在進行原型鏈繼承的時候也執行了一次,並且在原型鏈上生成了一個我們永遠也不需要訪問的name屬性,而這肯定是佔記憶體的(想象一下name不是一個字串,而是一個物件),呢麼我們怎麼能讓原型鏈繼承更純粹一點呢?讓它只繼承原型(靜態【屬性/方法】)呢?

5.寄生組合方式實現繼承
為了讓原型鏈繼承的更純粹,這裡我們引入一個Super函式,讓Father的原型寄生在Super的原型上,然後讓Son去繼承Super,最後我們把這個過程放到一個閉包內,這樣Super就不會汙染全域性變數啦,話不多說上程式碼:

// 程式碼4.4
function Father(name) {
    this.name = name
}
Father.prototype.sayName = function() {
    console.log('My name is ' + this.name)
}
function Son() {
    Father.apply(this, arguments)
}
(function () {
   function Super(){}
   Super.prototype = Father.prototype
   Son.prototype = new Super()
}())
var sonObj_1 = new Son('jack')

這個時候再去列印sonObj1就會發現,它的原型中已經沒有name屬性啦,如下所示:

看完這個之後,真的對js的原型鏈有了更深刻的理解。

在java中的繼承真是太簡單了 基本沒什麼可說的,就是繼承父類的屬性以及方法。

相關推薦

Javascript 原型繼承理解

自己 test 參數 自動 3.3 圖片 返回值 面向 指向 一、序言 ??和其他面向對象的語言(如Java)不同,Javascript語言對類的實現和繼承的實現沒有標準的定義,而是將這些交給了程序員,讓程序員更加靈活地(當然剛開始也更加頭疼)去定義類,實現繼承。(以下不討

Javascript 原型繼承理解

出處:https://www.cnblogs.com/codernie/p/9098184.html一、序言  和其他面向物件的語言(如Java)不同,Javascript語言對類的實現和繼承的實現沒有標準的定義,而是將這些交給了程式設計師,讓程式設計師更加靈活地(當然剛開始

深入理解JavaScript原型原型繼承

建構函式,原型物件,例項物件三者之間的關係 1.建構函式可以例項化物件 2.建構函式中有一個prototype屬性,是建構函式的原型物件 3.原型物件(prototype)中有constructor構造器指向的是當前原型物件所在的建構函式 4.例項物件的__proto__原型指向建

JavaScript物件原型繼承閉包

什麼是面向物件程式設計 說到面向物件,每個人的理解可能不同,以下是個人對面向物件程式設計的理解: 對於面向物件程式設計這幾個字每一個前端都應該非常熟悉,但是到底應該如何去理解他呢?就程式設計方式而言,javascrip可以分成兩種發方式:面向過程和麵向物件,所謂的面向過程就是比較常用的函數語言程式設計,通

JavaScript對象原型繼承閉包

增強 版本 data- 命名沖突 lock .com text t對象 單體 什麽是面向對象編程 說到面向對象,每個人的理解可能不同,以下是個人對面向對象編程的理解: 對於面向對象編程這幾個字每一個前端都應該非常熟悉,但是到底應該如何去理解他呢?就編程方式而言,javasc

JavaScript物件原型繼承和閉包

什麼是面向物件程式設計 說到面向物件,每個人的理解可能不同,以下是個人對面向物件程式設計的理解: 對於面向物件程式設計這幾個字每一個前端都應該非常熟悉,但是到底應該如何去理解他呢?就程式設計方式而言,javascrip可以分成兩種發方式:面向過程和麵向物件,所謂的面向過程就是比較常用的函數語言程式設計,通

JavaScript對象原型繼承和閉包

當我 tle 向上 之前 就會 控制 操作 rul 簡單介紹 什麽是面向對象編程 說到面向對象,每個人的理解可能不同,以下是個人對面向對象編程的理解: 對於面向對象編程這幾個字每一個前端都應該非常熟悉,但是到底應該如何去理解他呢?就編程方式而言,javascrip可以分成兩

面向對象原型繼承知識梳理

中介 獲取 混合 console 原型 code name 諸多 祖先 單例模式:就是一個對象咯 var person={ name:‘xuwen‘, age:17 }; var person2={ name:‘xiaoxu‘, age:2

關於對象構造函數原型原型繼承

mes 創建對象 light 表示 {} highlight col 面向 原型鏈 對象: 在傳統的面向過程的程序設計中,會造成函數或變量的冗余。而js中對象的目的是將所有的具有相同屬性或行為的代碼整合到一起,形成一個集合,這樣就會方便管理,例如: var person

前端綜合學習筆記---變量原型作用域和閉包

global log 屬性 關系 改變 def 再看 內存地址 入棧 變量類型   JavaScript 是一種弱類型腳本語言,所謂弱類型指的是定義變量時,不需要什麽類型,在程序運行過程中會自動判斷類型。 ECMAScript 中定義了 6 種原始類型: Boolean

JavaScript學習筆記——原型原型call/apply—day five

目錄 原型 定義 原型的增刪改查 物件如何檢視物件的建構函式 物件如何檢視原型 原型鏈 如何構成原型鏈 Object.create(原型)  call/apply 原型 定義 原型是function物件的一個屬性,它定義了建

談談JavaScript原型原型建構函式prototype__proto__和constructor

  原型、原型鏈、建構函式是JavaScript比較難的知識點,但是它們又功能強大,是我們進行元件開發、模組開發必須掌握的技能,翻閱網上相關的博文,個人覺得這幾篇博文不錯,可以一讀:   1)湯姆大叔:強大的原型和原型鏈   2)深入理解JavaScript系列(10):JavaScript

Js中的物件建構函式原型原型繼承

1、物件 在傳統的面向過程的程式設計中,會造成函式或變數的冗餘。而JS中物件的目的是將所有的具有相同屬性或行為的程式碼整合到一起,形成一個集合,這樣就會方便我們管理,例如: var person1={     name:"tan",     age:26,     sho

關於JS原型原型繼承的問題

怎麽 都是 this -s 不是函數 一個 prototype true function   任何對象都是被構造出來的,構造對象的方法稱為構造函數,構造函數生成的對象為構造函數的實例。聲明一個對象可以var obj = {},也可以var obj = new Object

JS概念理解(一)——函式和物件原型_proto_

        最近發現自己JS的基礎不太好,於是通過看書和大神的部落格,通過自己的理解將一些難懂的概念在此總結:         1.函式和物件的關係:         在JavaScript中一切物件都是通過函式建立的,某些情況下的寫法實際上是一種語法糖: var a

建構函式原型原型繼承

JS裡一切皆物件,物件是“無序屬性的集合,其屬性值可以是資料或函式”。 事實上,所有的物件都是由函式建立的,而常見的物件字面量則只是一種語法糖: ```js // let user = {name: 'paykan', age: 29} ,等同於: let user = new Object();

JavaScript中的原型原型原型模式

今天,咱來聊聊JavaScript中的原型跟原型鏈 # 原型跟原型模式 這一塊的知識,主要是設計模式方面的。 首先,我們知道JavaScript是面向物件的。既然是面向物件,那它自然也有相應的類跟物件等概念。 在JavaScript中,function這個東西還是比較特殊的,它既能用來宣告方法,還能用來宣告一

JavaScript中的原型繼承

工具 call logs 不難 code 也會 str 最簡 創建子類 理解原型鏈 在 JavaScript 的世界中,函數是一等公民。 上面這句話在很多地方都看到過。用我自己的話來理解就是:函數既當爹又當媽。“當爹”是因為我們用函數去處理各種“粗活累活”(各種工具函

原生JavaScript中的原型繼承(程式碼例項詳解)

       本文通過例子將原生JavaScript中的原型鏈和繼承進行介紹,在學習時可以將程式碼執行下,檢視下控制檯相關的輸出;在寫程式碼的同時添加了很多註釋,方便理解和程式碼分塊。例項程式碼如下:<!DOCTYPE html> <html> <

js系列教程13-原型原型作用閉包全解

全棧工程師開發手冊 (作者:欒鵬) 【物件、變數】 一個物件就是一個類,可以理解為一個物體的標準化定義。它不是一個具體的實物,只是一個標準。而通過物件例項化得到的變數就是一個獨立的實物。比如通過一個物件定義了“人”,通過“人”這個標準化定義,