目錄html
1.一切皆是對象嗎?編程
首先,「在 JavaScript 中,一切皆是對象」這種表述是不徹底正確的。瀏覽器
JavaScript 的數據類型分爲兩類:原始值類型和對象(Object類型)。函數
原始值類型(ES5):this
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)沒有對應的包裝器對象,因此在其上嘗試訪問任何屬性或方法都會報錯。以下圖:
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
上述代碼示例中的完整原型鏈關係以下圖:
從上圖咱們能夠總結:
全部的(隱式)原型鏈的最末端最終都會指向 null(JS不容許有循環原型鏈,避免死循環)
全部函數默認都是有 Function 構造函數建立,即全部函數的隱式原型(__proto__)都指向 Function.prototype。
全部對象默認都繼承自Object對象,即默認狀況下,全部對象的(隱式)原型鏈的末端都指向 Object.prototype。
注:所謂默認狀況,即沒有手動修改原型鏈關係。
3.參考