說明
圖解 Google V8 學習筆記
繼承是什麼?
簡單的說:繼承就是一個對象可以通路另外一個對象中的屬性和方法,在JavaScript 中,我們通過原型和原型鍊的方式來實作了繼承特性。。
不同的語言實作繼承的方式是不同的,其中最典型的兩種方式:
- 基于類的設計:C++、Java、C#
- 基于原型繼承的設計:JavaScript
原型繼承是如何實作的?
JavaScript 的每個對象都包含了一個隐藏屬性
__proto__
,我們就把該隐藏屬性
__proto__
稱之為該對象的原型 (prototype),
__proto__
指向了記憶體中的另外一個對象,我們就把
__proto__
指向的對象稱為該對象的原型對象。
例子:
我們讓 C 對象的原型指向 B 對象,讓 B 對象的原型指向 A 對象,那麼 C 對象就可以直接通路 B 以及 A 的方法跟屬性了。
當我們通過對象 C 來通路對象 A 中的 color 屬性時,V8 會先從對象 C 中查找,沒有查找到,接着繼續在 C 對象的原型對象 B 中查找,依舊沒有查找到,那麼繼續去對象 B 的原型對象 A 中查找,因為 color 在對象 A 中,那麼 V8 就傳回該屬性值。我們把這個查找屬性的路徑稱為原型鍊。
原型鍊 vs 作用域鍊
- 原型鍊:是沿着對象的原型一級一級來查找屬性的
- 作用域鍊:是沿着函數的作用域一級一級來查找變量的
實踐:利用 __proto__
實作繼承
__proto__
下面先建立了兩個對象 animal 和 dog,如果讓 dog 對象繼承于 animal 對象,應該怎麼操作?
var animal = {
type: "Default",
color: "Default",
getInfo: function () {
return `Type is: ${this.type},color is ${this.color}.`
}
}
var dog = {
type: "Dog",
color: "Black",
}
最直接的方式就是通過設定 dog 對象中的
__proto__
屬性,将其指向 animal。
dog.__proto__ =
使用 dog 來調用 animal 中的 getInfo 方法
dog.getInfo()
輸出結果如下:
注意:通常隐藏屬性是不能使用 JavaScript 來直接與之互動的。雖然現代浏覽器都開了一個口子,讓 JavaScript 可以通路隐藏屬性
_proto_
,但是在實際項目中,我們不應該直接通過
_proto_
來通路或者修改該屬性,應該使用構造函數來建立對象。
其主要原因有兩個:
-
是隐藏屬性,并不是标準定義的 ;_proto_
- 原型的實作做了很多複雜的優化,比如:通過隐藏類優化了很多原有的對象結構,是以通過直接修改
會直接破壞現有已經優化的結構,造成嚴重的性能問題。__proto__
構造函數是怎麼建立對象的?
例子:
- 先建立一個 DogFactory 的函數,屬性通過參數進行傳遞,在函數體内,通過 this 設定屬性值。
function DogFactory(type,){
this.type = type
this.color = color
}
- 再結合關鍵字
就可以建立對象(DogFactory 函數稱為構造函數)new
var dog = new DogFactory('Dog', 'Black')
V8 執行上面這段代碼時,做了什麼?
大緻分為三步:
- 建立了一個空白對象 dog
- 将 DogFactory 的 prototype 屬性設定為 dog 的原型對象
- 再使用 dog 來調用 DogFactory,這時候 DogFactory 函數中的 this 就指向了對象 dog,然後在 DogFactory 函數中,利用 this 對對象 dog 執行屬性填充操作
最終就建立了對象 dog。
模拟代碼如下:
var dog = {}
dog.__proto__ = DogFactory.prototype
DogFactory.call(dog, 'Dog', 'Black')
執行流程圖示意圖:
構造函數怎麼實作繼承?
例子:添加 constant_temperature 為 1 表示恒溫動物
function DogFactory(type,color){
this.type = type
this.color = color
// 恒溫動物
this.constant_temperature = 1
}
var dog1 = new DogFactory('Dog','Black')
var dog2 = new DogFactory('Dog','Black')
var dog3 = new DogFactory('Dog','Black')
dog1、dog2、dog3 占用空間示意圖:
可以看到 constant_temperature 屬性都占用了一塊空間,因為 dog 是恒溫動物,每個對象 沒必要為 constant_temperature 屬性都配置設定一塊空間,該屬性既然是通用的,可以設定屬性為公用的。
每個函數對象中都有一個公開的 prototype 屬性,當這個函數作為構造函數來建立一個新的對象時,新建立對象的原型對象就指向了該函數的 prototype 屬性。
三個 dog 對象的原型對象都指向了 prototype,我們隻要讓 prototype 包含 constant_temperature 屬性,就能實作繼承了。
function DogFactory(type,color){
this.type = type
this.color = color
}
DogFactory. prototype.constant_temperature = 1
var dog1 = new DogFactory('Dog','Black')
var dog2 = new DogFactory('Dog','Black')
var dog3 = new DogFactory('Dog','Black')
構造函數的 __proto__
和 prototype
__proto__
- 函數作為對象他得擁有一個
,該屬性是隐藏屬性,并不是标準定義的 ;__proto__
- 函數作為一個構造函數,它得擁有一個
,該屬性是标準定義的prototype
上面的
DogFactory
是
Function
構造函數的一個執行個體,是以
DogFactory.__proto__ === Function.prototype
;
DogFactory.prototype
是調用 Object 構造函數的一個執行個體,是以
DogFactory.prototype.__proto__ === Object.prototype
;
是以
DogFactory._proto_
和
DogFactory.prototype
沒有直接關系。