'「前端詞典」必備知識-原型與原型鏈'

"

前言

繼承是我們前端必須熟悉的一個知識點。可依舊有很多前端對繼承的實現和應用沒有一個整體的把握。追其原因無非有二:

  • ECMAScript 繼承的實現方法區別於其他基於類的實現繼承的面向對象(Object Oriented)語言。
  • 工作中即使對如何實現繼承一知半解,也一點都不耽誤寫邏輯代碼。

無論由於哪一個原因,建議請儘快弄懂繼承的實現和應用,否則你可能會如同你的表情包一樣——流下了沒有技術的淚水。

接下來我會盡我所能講清楚繼承這個概念,並結合相關經典圖文做輔助解釋。

在講 ECMAScript 繼承的概念之前,我先說下原型的概念。

類與原型

講 ECMAScript 繼承的概念之前,我先說下類的概念。(如果接觸過 Java 或者是 C++ 的話,我們就知道 Java(C++)的繼承都是基於類的繼承)。

類: 是面向對象(Object Oriented)語言實現信息封裝的基礎,稱為類類型。每個類包含數據說明和一組操作數據或傳遞消息的函數。類的實例稱為對象

類: 是描述了一種代碼的組織結構形式,一種在軟件中對真實世界中問題領域的建模方法。

類的概念這裡我就不再擴展,感興趣的同學可以自行查閱書籍。接下來我們重點講講原型以及原型鏈

原型

JavaScript 這門語言沒有類的概念,所以 JavaScript 並非是基於類的繼承,而是基於原型的繼承。(主要是借鑑 Self 語言原型( prototype)繼承機制)。

注意:ES6 中的 class 關鍵字和 OO 語言中的類的概念是不同的,下面我會講到。ES6 的 class 其內部同樣是基於原型實現的繼承。

JavaScript 摒棄轉而使用原型作為實現繼承的基礎,是因為基於原型的繼承相比基於的繼承上在概念上更為簡單。首先我們明確一點,存在的目的是為了實例化對象,而 JavaScript 可以直接通過對象字面量語法輕鬆的創建對象。

每一個函數,都有一個 prototype 屬性。

所有通過函數 new 出來的對象,這個對象都有一個 __proto__ 指向這個函數的 prototype。

當你想要使用一個對象(或者一個數組)的某個功能時:如果該對象本身具有這個功能,則直接使用;如果該對象本身沒有這個功能,則去 __proto__ 中找。

1. prototype [顯式原型]

prototype 是一個顯式的原型屬性,只有函數才擁有該屬性。

每一個函數在創建之後都會擁有一個名為 prototype 的屬性,這個屬性指向函數的原型對象。( 通過 Function.prototype.bind 方法構造出來的函數是個例外,它沒有 prototype 屬性 )。

prototype 是一個指針,指向的是一個對象。比如 Array.prototype 指向的就是 Array 這個函數的原型對象。

"

前言

繼承是我們前端必須熟悉的一個知識點。可依舊有很多前端對繼承的實現和應用沒有一個整體的把握。追其原因無非有二:

  • ECMAScript 繼承的實現方法區別於其他基於類的實現繼承的面向對象(Object Oriented)語言。
  • 工作中即使對如何實現繼承一知半解,也一點都不耽誤寫邏輯代碼。

無論由於哪一個原因,建議請儘快弄懂繼承的實現和應用,否則你可能會如同你的表情包一樣——流下了沒有技術的淚水。

接下來我會盡我所能講清楚繼承這個概念,並結合相關經典圖文做輔助解釋。

在講 ECMAScript 繼承的概念之前,我先說下原型的概念。

類與原型

講 ECMAScript 繼承的概念之前,我先說下類的概念。(如果接觸過 Java 或者是 C++ 的話,我們就知道 Java(C++)的繼承都是基於類的繼承)。

類: 是面向對象(Object Oriented)語言實現信息封裝的基礎,稱為類類型。每個類包含數據說明和一組操作數據或傳遞消息的函數。類的實例稱為對象

類: 是描述了一種代碼的組織結構形式,一種在軟件中對真實世界中問題領域的建模方法。

類的概念這裡我就不再擴展,感興趣的同學可以自行查閱書籍。接下來我們重點講講原型以及原型鏈

原型

JavaScript 這門語言沒有類的概念,所以 JavaScript 並非是基於類的繼承,而是基於原型的繼承。(主要是借鑑 Self 語言原型( prototype)繼承機制)。

注意:ES6 中的 class 關鍵字和 OO 語言中的類的概念是不同的,下面我會講到。ES6 的 class 其內部同樣是基於原型實現的繼承。

JavaScript 摒棄轉而使用原型作為實現繼承的基礎,是因為基於原型的繼承相比基於的繼承上在概念上更為簡單。首先我們明確一點,存在的目的是為了實例化對象,而 JavaScript 可以直接通過對象字面量語法輕鬆的創建對象。

每一個函數,都有一個 prototype 屬性。

所有通過函數 new 出來的對象,這個對象都有一個 __proto__ 指向這個函數的 prototype。

當你想要使用一個對象(或者一個數組)的某個功能時:如果該對象本身具有這個功能,則直接使用;如果該對象本身沒有這個功能,則去 __proto__ 中找。

1. prototype [顯式原型]

prototype 是一個顯式的原型屬性,只有函數才擁有該屬性。

每一個函數在創建之後都會擁有一個名為 prototype 的屬性,這個屬性指向函數的原型對象。( 通過 Function.prototype.bind 方法構造出來的函數是個例外,它沒有 prototype 屬性 )。

prototype 是一個指針,指向的是一個對象。比如 Array.prototype 指向的就是 Array 這個函數的原型對象。

「前端詞典」必備知識-原型與原型鏈

在控制檯中打印 console.log(Array.prototype) 裡面有很多方法。這些方法都以事先內置在 JavaScript 中,直接調用即可。上面我標紅了兩個特別的屬性 constructor 和 __proto__。這兩個屬性接下來我都會講。

我們現在寫一個 functionnoWork(){} 函數。

"

前言

繼承是我們前端必須熟悉的一個知識點。可依舊有很多前端對繼承的實現和應用沒有一個整體的把握。追其原因無非有二:

  • ECMAScript 繼承的實現方法區別於其他基於類的實現繼承的面向對象(Object Oriented)語言。
  • 工作中即使對如何實現繼承一知半解,也一點都不耽誤寫邏輯代碼。

無論由於哪一個原因,建議請儘快弄懂繼承的實現和應用,否則你可能會如同你的表情包一樣——流下了沒有技術的淚水。

接下來我會盡我所能講清楚繼承這個概念,並結合相關經典圖文做輔助解釋。

在講 ECMAScript 繼承的概念之前,我先說下原型的概念。

類與原型

講 ECMAScript 繼承的概念之前,我先說下類的概念。(如果接觸過 Java 或者是 C++ 的話,我們就知道 Java(C++)的繼承都是基於類的繼承)。

類: 是面向對象(Object Oriented)語言實現信息封裝的基礎,稱為類類型。每個類包含數據說明和一組操作數據或傳遞消息的函數。類的實例稱為對象

類: 是描述了一種代碼的組織結構形式,一種在軟件中對真實世界中問題領域的建模方法。

類的概念這裡我就不再擴展,感興趣的同學可以自行查閱書籍。接下來我們重點講講原型以及原型鏈

原型

JavaScript 這門語言沒有類的概念,所以 JavaScript 並非是基於類的繼承,而是基於原型的繼承。(主要是借鑑 Self 語言原型( prototype)繼承機制)。

注意:ES6 中的 class 關鍵字和 OO 語言中的類的概念是不同的,下面我會講到。ES6 的 class 其內部同樣是基於原型實現的繼承。

JavaScript 摒棄轉而使用原型作為實現繼承的基礎,是因為基於原型的繼承相比基於的繼承上在概念上更為簡單。首先我們明確一點,存在的目的是為了實例化對象,而 JavaScript 可以直接通過對象字面量語法輕鬆的創建對象。

每一個函數,都有一個 prototype 屬性。

所有通過函數 new 出來的對象,這個對象都有一個 __proto__ 指向這個函數的 prototype。

當你想要使用一個對象(或者一個數組)的某個功能時:如果該對象本身具有這個功能,則直接使用;如果該對象本身沒有這個功能,則去 __proto__ 中找。

1. prototype [顯式原型]

prototype 是一個顯式的原型屬性,只有函數才擁有該屬性。

每一個函數在創建之後都會擁有一個名為 prototype 的屬性,這個屬性指向函數的原型對象。( 通過 Function.prototype.bind 方法構造出來的函數是個例外,它沒有 prototype 屬性 )。

prototype 是一個指針,指向的是一個對象。比如 Array.prototype 指向的就是 Array 這個函數的原型對象。

「前端詞典」必備知識-原型與原型鏈

在控制檯中打印 console.log(Array.prototype) 裡面有很多方法。這些方法都以事先內置在 JavaScript 中,直接調用即可。上面我標紅了兩個特別的屬性 constructor 和 __proto__。這兩個屬性接下來我都會講。

我們現在寫一個 functionnoWork(){} 函數。

「前端詞典」必備知識-原型與原型鏈

當我寫了一個 noWork 這個方法的時候,它自動創建了一個 prototype 指針屬性(指向原型對象)。而這個被指向的原型對象自動獲得了一個 constructor (構造函數)。細心的同學一定發現了: constructor 指向的是 noWork。

noWork.prototype.constructor ===noWork // true
// 一個函數的原型對象的構造函數是這個函數本身

tips: 圖中打印的 Array 的顯式原型對象中的這些方法你都知道嗎?要知道數組也是非常重要的一部分哦 ~ 咳咳咳,這是考試重點。

2. __proto__[隱式原型]

prototype 理解起來不難, __proto__ 理解起來就會比 prototype 稍微複雜一點。不過當你理解的時候你會發現,這個過程真的很有趣。下面我們就講講 __proto__。

其實這個屬性指向了 `[[prototype]]`,但是 `[[prototype]]` 是內部屬性,我們並不能訪問到,所以使用 `__proto__` 來訪問。

我先給個有點繞的定義:

__proto__ 指向了創建該對象的構造函數的顯式原型。

我們現在還是使用 noWork 這個例子來說。我們發現 noWork 原型對象中還有另一個屬性 __proto__。

我們先打印這個屬性:

"

前言

繼承是我們前端必須熟悉的一個知識點。可依舊有很多前端對繼承的實現和應用沒有一個整體的把握。追其原因無非有二:

  • ECMAScript 繼承的實現方法區別於其他基於類的實現繼承的面向對象(Object Oriented)語言。
  • 工作中即使對如何實現繼承一知半解,也一點都不耽誤寫邏輯代碼。

無論由於哪一個原因,建議請儘快弄懂繼承的實現和應用,否則你可能會如同你的表情包一樣——流下了沒有技術的淚水。

接下來我會盡我所能講清楚繼承這個概念,並結合相關經典圖文做輔助解釋。

在講 ECMAScript 繼承的概念之前,我先說下原型的概念。

類與原型

講 ECMAScript 繼承的概念之前,我先說下類的概念。(如果接觸過 Java 或者是 C++ 的話,我們就知道 Java(C++)的繼承都是基於類的繼承)。

類: 是面向對象(Object Oriented)語言實現信息封裝的基礎,稱為類類型。每個類包含數據說明和一組操作數據或傳遞消息的函數。類的實例稱為對象

類: 是描述了一種代碼的組織結構形式,一種在軟件中對真實世界中問題領域的建模方法。

類的概念這裡我就不再擴展,感興趣的同學可以自行查閱書籍。接下來我們重點講講原型以及原型鏈

原型

JavaScript 這門語言沒有類的概念,所以 JavaScript 並非是基於類的繼承,而是基於原型的繼承。(主要是借鑑 Self 語言原型( prototype)繼承機制)。

注意:ES6 中的 class 關鍵字和 OO 語言中的類的概念是不同的,下面我會講到。ES6 的 class 其內部同樣是基於原型實現的繼承。

JavaScript 摒棄轉而使用原型作為實現繼承的基礎,是因為基於原型的繼承相比基於的繼承上在概念上更為簡單。首先我們明確一點,存在的目的是為了實例化對象,而 JavaScript 可以直接通過對象字面量語法輕鬆的創建對象。

每一個函數,都有一個 prototype 屬性。

所有通過函數 new 出來的對象,這個對象都有一個 __proto__ 指向這個函數的 prototype。

當你想要使用一個對象(或者一個數組)的某個功能時:如果該對象本身具有這個功能,則直接使用;如果該對象本身沒有這個功能,則去 __proto__ 中找。

1. prototype [顯式原型]

prototype 是一個顯式的原型屬性,只有函數才擁有該屬性。

每一個函數在創建之後都會擁有一個名為 prototype 的屬性,這個屬性指向函數的原型對象。( 通過 Function.prototype.bind 方法構造出來的函數是個例外,它沒有 prototype 屬性 )。

prototype 是一個指針,指向的是一個對象。比如 Array.prototype 指向的就是 Array 這個函數的原型對象。

「前端詞典」必備知識-原型與原型鏈

在控制檯中打印 console.log(Array.prototype) 裡面有很多方法。這些方法都以事先內置在 JavaScript 中,直接調用即可。上面我標紅了兩個特別的屬性 constructor 和 __proto__。這兩個屬性接下來我都會講。

我們現在寫一個 functionnoWork(){} 函數。

「前端詞典」必備知識-原型與原型鏈

當我寫了一個 noWork 這個方法的時候,它自動創建了一個 prototype 指針屬性(指向原型對象)。而這個被指向的原型對象自動獲得了一個 constructor (構造函數)。細心的同學一定發現了: constructor 指向的是 noWork。

noWork.prototype.constructor ===noWork // true
// 一個函數的原型對象的構造函數是這個函數本身

tips: 圖中打印的 Array 的顯式原型對象中的這些方法你都知道嗎?要知道數組也是非常重要的一部分哦 ~ 咳咳咳,這是考試重點。

2. __proto__[隱式原型]

prototype 理解起來不難, __proto__ 理解起來就會比 prototype 稍微複雜一點。不過當你理解的時候你會發現,這個過程真的很有趣。下面我們就講講 __proto__。

其實這個屬性指向了 `[[prototype]]`,但是 `[[prototype]]` 是內部屬性,我們並不能訪問到,所以使用 `__proto__` 來訪問。

我先給個有點繞的定義:

__proto__ 指向了創建該對象的構造函數的顯式原型。

我們現在還是使用 noWork 這個例子來說。我們發現 noWork 原型對象中還有另一個屬性 __proto__。

我們先打印這個屬性:

「前端詞典」必備知識-原型與原型鏈

我們發現這個 __proto__ 指向的是 Object.prototype。

我聽到有人在問為什麼?

  1. 因為這個 __proto__.constructor 指向的是 Object。
  2. 我們知道:一個函數的原型對象的構造函數是這個函數本身
  3. 所以這個 __proto__.constructor 指向的是 Object.prototype.constructor。
  4. 進而 __proto__ 指向的是 Object.prototype。
至於為什麼是指向Object?因為所有的引用類型默認都是繼承Object。

作用

  1. 顯式原型:用來實現基於原型的繼承與屬性的共享。
  2. 隱式原型:構成原型鏈,同樣用於實現基於原型的繼承。 舉個例子,當我們使用 noWork 這個對象中的 toString() 屬性時,在 noWork 中找不到,就會沿著 __proto__ 依次查找。

3. new 操作符

當我們使用 new 操作符時,生成的實例對象擁有了 __proto__屬性。即在 new 的過程中,新對象被添加了 __proto__ 並且鏈接到構造函數的原型上。

new 的過程

  1. 新生成了一個對象
  2. 鏈接到原型
  3. 綁定 this
  4. 返回新對象

Function.__proto__===Function.prototype

難道這代表著 Function 自己產生了自己? 要說明這個問題我們先從 Object 說起。

我們知道所有對象都可以通過原型鏈最終找到 Object.prototype ,雖然 Object.prototype 也是一個對象,但是這個對象卻不是 Object 創造的,而是引擎自己創建了 Object.prototype 。 所以可以這樣說:

所有實例都是對象,但是對象不一定都是實例。

接下來我們來看 Function.prototype 這個特殊的對象:

打印這個對象,會發現這個對象其實是一個函數。我們知道函數都是通過 newFunction()生成的,難道 Function.prototype 也是通過 newFunction() 產生的嗎?這個函數也是引擎自己創建的。

首先引擎創建了 Object.prototype ,然後創建了 Function.prototype ,並且通過 __proto__ 將兩者聯繫了起來。

這就是為什麼 Function.prototype.bind() 沒有 prototype 屬性。因為 Function.prototype 是引擎創建出來的對象,引擎認為不需要給這個對象添加 prototype 屬性。

對於為什麼 Function.__proto__ 會等於 Function.prototype ?

我看到的一個解釋是這樣的:

其他所有的構造函數都可以通過原型鏈找到 Function.prototype ,並且 functionFunction() 本質也是一個函數,為了不產生混亂就將 functionFunction() 的 __proto__ 聯繫到了 Function.prototype 上。

"

前言

繼承是我們前端必須熟悉的一個知識點。可依舊有很多前端對繼承的實現和應用沒有一個整體的把握。追其原因無非有二:

  • ECMAScript 繼承的實現方法區別於其他基於類的實現繼承的面向對象(Object Oriented)語言。
  • 工作中即使對如何實現繼承一知半解,也一點都不耽誤寫邏輯代碼。

無論由於哪一個原因,建議請儘快弄懂繼承的實現和應用,否則你可能會如同你的表情包一樣——流下了沒有技術的淚水。

接下來我會盡我所能講清楚繼承這個概念,並結合相關經典圖文做輔助解釋。

在講 ECMAScript 繼承的概念之前,我先說下原型的概念。

類與原型

講 ECMAScript 繼承的概念之前,我先說下類的概念。(如果接觸過 Java 或者是 C++ 的話,我們就知道 Java(C++)的繼承都是基於類的繼承)。

類: 是面向對象(Object Oriented)語言實現信息封裝的基礎,稱為類類型。每個類包含數據說明和一組操作數據或傳遞消息的函數。類的實例稱為對象

類: 是描述了一種代碼的組織結構形式,一種在軟件中對真實世界中問題領域的建模方法。

類的概念這裡我就不再擴展,感興趣的同學可以自行查閱書籍。接下來我們重點講講原型以及原型鏈

原型

JavaScript 這門語言沒有類的概念,所以 JavaScript 並非是基於類的繼承,而是基於原型的繼承。(主要是借鑑 Self 語言原型( prototype)繼承機制)。

注意:ES6 中的 class 關鍵字和 OO 語言中的類的概念是不同的,下面我會講到。ES6 的 class 其內部同樣是基於原型實現的繼承。

JavaScript 摒棄轉而使用原型作為實現繼承的基礎,是因為基於原型的繼承相比基於的繼承上在概念上更為簡單。首先我們明確一點,存在的目的是為了實例化對象,而 JavaScript 可以直接通過對象字面量語法輕鬆的創建對象。

每一個函數,都有一個 prototype 屬性。

所有通過函數 new 出來的對象,這個對象都有一個 __proto__ 指向這個函數的 prototype。

當你想要使用一個對象(或者一個數組)的某個功能時:如果該對象本身具有這個功能,則直接使用;如果該對象本身沒有這個功能,則去 __proto__ 中找。

1. prototype [顯式原型]

prototype 是一個顯式的原型屬性,只有函數才擁有該屬性。

每一個函數在創建之後都會擁有一個名為 prototype 的屬性,這個屬性指向函數的原型對象。( 通過 Function.prototype.bind 方法構造出來的函數是個例外,它沒有 prototype 屬性 )。

prototype 是一個指針,指向的是一個對象。比如 Array.prototype 指向的就是 Array 這個函數的原型對象。

「前端詞典」必備知識-原型與原型鏈

在控制檯中打印 console.log(Array.prototype) 裡面有很多方法。這些方法都以事先內置在 JavaScript 中,直接調用即可。上面我標紅了兩個特別的屬性 constructor 和 __proto__。這兩個屬性接下來我都會講。

我們現在寫一個 functionnoWork(){} 函數。

「前端詞典」必備知識-原型與原型鏈

當我寫了一個 noWork 這個方法的時候,它自動創建了一個 prototype 指針屬性(指向原型對象)。而這個被指向的原型對象自動獲得了一個 constructor (構造函數)。細心的同學一定發現了: constructor 指向的是 noWork。

noWork.prototype.constructor ===noWork // true
// 一個函數的原型對象的構造函數是這個函數本身

tips: 圖中打印的 Array 的顯式原型對象中的這些方法你都知道嗎?要知道數組也是非常重要的一部分哦 ~ 咳咳咳,這是考試重點。

2. __proto__[隱式原型]

prototype 理解起來不難, __proto__ 理解起來就會比 prototype 稍微複雜一點。不過當你理解的時候你會發現,這個過程真的很有趣。下面我們就講講 __proto__。

其實這個屬性指向了 `[[prototype]]`,但是 `[[prototype]]` 是內部屬性,我們並不能訪問到,所以使用 `__proto__` 來訪問。

我先給個有點繞的定義:

__proto__ 指向了創建該對象的構造函數的顯式原型。

我們現在還是使用 noWork 這個例子來說。我們發現 noWork 原型對象中還有另一個屬性 __proto__。

我們先打印這個屬性:

「前端詞典」必備知識-原型與原型鏈

我們發現這個 __proto__ 指向的是 Object.prototype。

我聽到有人在問為什麼?

  1. 因為這個 __proto__.constructor 指向的是 Object。
  2. 我們知道:一個函數的原型對象的構造函數是這個函數本身
  3. 所以這個 __proto__.constructor 指向的是 Object.prototype.constructor。
  4. 進而 __proto__ 指向的是 Object.prototype。
至於為什麼是指向Object?因為所有的引用類型默認都是繼承Object。

作用

  1. 顯式原型:用來實現基於原型的繼承與屬性的共享。
  2. 隱式原型:構成原型鏈,同樣用於實現基於原型的繼承。 舉個例子,當我們使用 noWork 這個對象中的 toString() 屬性時,在 noWork 中找不到,就會沿著 __proto__ 依次查找。

3. new 操作符

當我們使用 new 操作符時,生成的實例對象擁有了 __proto__屬性。即在 new 的過程中,新對象被添加了 __proto__ 並且鏈接到構造函數的原型上。

new 的過程

  1. 新生成了一個對象
  2. 鏈接到原型
  3. 綁定 this
  4. 返回新對象

Function.__proto__===Function.prototype

難道這代表著 Function 自己產生了自己? 要說明這個問題我們先從 Object 說起。

我們知道所有對象都可以通過原型鏈最終找到 Object.prototype ,雖然 Object.prototype 也是一個對象,但是這個對象卻不是 Object 創造的,而是引擎自己創建了 Object.prototype 。 所以可以這樣說:

所有實例都是對象,但是對象不一定都是實例。

接下來我們來看 Function.prototype 這個特殊的對象:

打印這個對象,會發現這個對象其實是一個函數。我們知道函數都是通過 newFunction()生成的,難道 Function.prototype 也是通過 newFunction() 產生的嗎?這個函數也是引擎自己創建的。

首先引擎創建了 Object.prototype ,然後創建了 Function.prototype ,並且通過 __proto__ 將兩者聯繫了起來。

這就是為什麼 Function.prototype.bind() 沒有 prototype 屬性。因為 Function.prototype 是引擎創建出來的對象,引擎認為不需要給這個對象添加 prototype 屬性。

對於為什麼 Function.__proto__ 會等於 Function.prototype ?

我看到的一個解釋是這樣的:

其他所有的構造函數都可以通過原型鏈找到 Function.prototype ,並且 functionFunction() 本質也是一個函數,為了不產生混亂就將 functionFunction() 的 __proto__ 聯繫到了 Function.prototype 上。

「前端詞典」必備知識-原型與原型鏈

公眾號:Web前端Talk
"

相關推薦

推薦中...