深刻理解JS中的對象(一):原型、原型鏈和構造函數

目錄html

  • 一切皆是對象嗎?
  • 對象
    • 原型與原型鏈
    • 構造函數
  • 參考

1.一切皆是對象嗎?編程

首先,「在 JavaScript 中,一切皆是對象」這種表述是不徹底正確的。瀏覽器

JavaScript 的數據類型分爲兩類:原始值類型和對象(Object類型)。函數

原始值類型(ES5):this

  • undefined
  • null - typeof null 的值爲"object",是由於 ES5 規範規定:對於 null 值的 typeof 字符串值返回"object"
  • true/false - 布爾值
  • number
  • string
var a = undefined
var b = null
var c = true
var d = 1
var e = "abc"

這些值是在底層上直接實現的,它們不是object,因此沒有原型(__proto__),沒有構造函數(constructor)。prototype

但咱們再實踐過程當中,會發現雖然字符串,布爾值和數字是原始值類型,但卻表現得有點像對象。3d

以字符串爲例:code

字符串原始值類型

在上圖中,能夠看到定義了一個值爲"abc"的字符串變量 e,訪問其 _proto_ 和 constructor 屬性,發現其竟然有值?不是說原始值類型沒有原型和構造函數,這是怎麼回事呢?htm

原來原始值類型(布爾值、數字、字符串)有其對應的包裝器對象:Boolean(布爾對象)、Number(數字對象)、String(字符串對象),在這些原始值類型上嘗試調用屬性或方法(好比 constructor 等)時,JS會自動進行 Auto-Boxing(臨時包裝)的過程,首先將其轉換爲臨時包裝器對象,再訪問其上的屬性或方法,而不會影響原始值類型的屬性。對象

這也能解釋爲何咱們直接對原始值類型變量(布爾值、數字、字符串)添加了一些屬性,再次訪問依舊爲 undefined,由於咱們訪問屬性操做的是臨時包裝器對象,不會影響基本原始值類型自己。以下圖:

臨時包裝器對象

而原始值類型(null 與 undefined)沒有對應的包裝器對象,因此在其上嘗試訪問任何屬性或方法都會報錯。以下圖:

null 與 undefined 沒有包裝器對象


2.對象

在JS中,Object 是一個屬性的集合,而且擁有一個單獨的原型對象 [prototype object] (其能夠是一個 object 或 null 值)。

在瀏覽器或 Node.js 中,能夠經過 _proto_ 屬性訪問這個原型對象, _proto_ 被稱爲該對象的原型,但爲了和函數的原型屬性(prototype)區分,通常稱其爲隱式原型。

var position = {
  x: 10,
  y: 20,
  z: 30,
}

上面的代碼中,對象與隱式原型的關係以下圖:

對象與隱式原型的關係


(1)原型與原型鏈

在JS中,對象的繼承關係是經過隱式原型(__proto__)來實現的。對象的隱式原型在對象建立時由對象的構造函數自動關聯,也能夠經過修改隱式原型,更改對象的繼承關係。

由 Object 構造函數建立的對象,其隱式原型指向 Object.prototype。而 Object.prototype 對象的隱式原型的值默認爲 nulll。

代碼示例:

// x, y, z 的隱式原型 __proto__ 默認都指向 Object.prototype
var x = {
    a: 10,
}
var y = {
    a: 20,
    b: 30,
}
var z = {
    a: 40,
    b: 50,
    c: 60,
}
  
// 設置 x 的隱式原型爲 y
// 設置 y 的隱式原型爲 z
x.__proto__ = y
y.__proto__ = z
  
console.log(x.a)  // 10 - 來自 x 自身的屬性
console.log(x.b)  // 30 - 來自 y 的屬性
console.log(x.c)  // 60 - 來自 z 的屬性

// 修改 y 的屬性 b 的值
y.b = 70

console.log(x.b)  // 70 - 來自 y 的屬性

// 移除 z 的屬性 c
delete z.c

console.log(x.c)  // undefined - 沿着隱式原型一級一級往上找,沒有找到該屬性

從上述代碼,咱們能夠看到,當訪問一個對象的屬性時,會優先在這個對象的屬性中查找是否存在所要訪問的屬性,若存在,則獲取成功,中止查找;若沒有找到該屬性,則會繼續去查找該對象的隱式原型中是否存在,若存在,則獲取成功,中止查找;若仍是沒有查找到,將繼續再往上一級的隱式原型中查找,直到找到則返回找到的屬性值 或 直到遇到隱式原型值爲 null 則返回 undefined。

這種由原型相互關聯(指向)的關係就造成了所謂的原型鏈,而對象的屬性或方法的查找就是沿着原型鏈順序進行查找的。

上述代碼示例中的原型鏈關係以下圖:

代碼示例中的原型鏈關係


(2)構造函數

首先要明白,函數也是一個特殊的對象,除了和其餘對象同樣有 _proto_ 屬性外,還有本身特有的屬性——顯示原型(prototype),這個屬性指向一個對象,其用途就是包含全部實例共享的屬性和方法。顯示原型對象也有一個 constructor 屬性,這個屬性指向原構造函數。

而所謂構造函數,就是提供一個生成對象的模板,並描述對象的基本結構的函數。一個構造函數,能夠生成多個對象,每一個對象都有相同的結構。而JS中全部函數(除了箭頭函數)均可以當作構造函數。

一個對象由構造函數建立時,其隱式原型(__proto__)指向構造該對象的構造函數(constructor)的顯示原型(prototype),這保證了實例可以訪問在構造函數原型中定義的屬性和方法。

代碼示例:

// 構造函數 C
function C(x) {
    this.x = x
}

// 繼承屬性 y
C.prototype.y = 30

// new 兩個對象實例a、b
var a = new C(10)
var b = new C(20)

console.log(a.x) // 10
console.log(a.y) // 30
console.log(b.x) // 20
console.log(b.y) // 30

// 對象實例 a、b 的隱式原型(__proto__)指向構造該對象的構造函數 C 的顯示原型(prototype)
console.log(a.__proto__ === C.prototype) // true
console.log(b.__proto__ === C.prototype) // true

// 構造函數的顯示原型(prototype)的 constructor 屬性指向原構造函數
console.log(C === C.prototype.constructor) // true

// 構造函數 C、Function 與 Object 的隱式原型(__proto__)指向構造該對象的構造函數 Function 的顯示原型(prototype)
console.log(C.__proto__ === Function.prototype) // true
console.log(Function.__proto__ === Function.prototype) // true
console.log(Object.__proto__ === Function.prototype) // true

// C.prototype 與 Function.prototype 的隱式原型(__proto__)指向構造該對象的構造函數 Object 的顯示原型(prototype)
console.log(C.prototype.__proto__ === Object.prototype) // true
console.log(Function.prototype.__proto__ === Object.prototype) // true

// Object.prototype 的隱式原型(__proto__)等於 null
console.log(Object.prototype.__proto__ === null) // true

上述代碼示例中的完整原型鏈關係以下圖:

代碼示例中的完整原型鏈關係

從上圖咱們能夠總結:

  1. 全部的(隱式)原型鏈的最末端最終都會指向 null(JS不容許有循環原型鏈,避免死循環)

  2. 全部函數默認都是有 Function 構造函數建立,即全部函數的隱式原型(__proto__)都指向 Function.prototype。

  3. 全部對象默認都繼承自Object對象,即默認狀況下,全部對象的(隱式)原型鏈的末端都指向 Object.prototype。

注:所謂默認狀況,即沒有手動修改原型鏈關係。


3.參考

JS中一切都是對象嗎?看這一篇就知道了

js中__proto__和prototype的區別和關係?

深刻理解JavaScript系列(10):JavaScript核心(晉級高手必讀篇)

深刻理解JavaScript系列(18):面向對象編程之ECMAScript實現(推薦)

相關文章
相關標籤/搜索