JavaScript 中的對象有一個特殊的 [[Prototype]] 內置屬性,其實就是對於其餘對象的引用。幾乎全部的對象在建立時 [[Prototype]] 屬性都會被賦予一個非空的值。javascript
思考一下以下代碼:java
var myObject = { a:2 }; myObject.a; // 2
當你試圖引用對象數據訪問屬性時會觸發對象默認的內置[[Get]]操做,按照[[Get]]操做的算法首先從myObject對象內部查找是否有相同名稱的屬性,若是找到就會放回這個屬性的值。算法
若是[[Get]]操做對象內部沒法找到須要的屬性,那麼繼續訪問對象的[[prototype]]鏈:函數
let anotherObject = { a:2 }; // 建立一個關聯到 anotherObject 的對象 let myObject = Object.create( anotherObject ); myObject.a; // 2
如今 myObject 對象的 [[Prototype]] 關聯到了 anotherObject。顯然 myObject.a 並不存在,可是儘管如此,屬性訪問仍然成功地(在 anotherObject 中)找到了值 2。可是若是 anotherObject 中也找不到 a 而且 [[Prototype]] 鏈不爲空的話,就會繼續查找下去。這個過程會持續到找到匹配的屬性名或者查找完整條 [[Prototype]] 鏈。若是是後者的話,[[Get]] 操做的返回值是 undefined。
使用 for..in 遍歷對象時原理和查找 [[Prototype]] 鏈相似,任何能夠經過原型鏈訪問到的屬性都會被枚舉。使用 in 操做符來檢查屬性在對象中是否存在時,一樣會查找對象的整條原型鏈(不管屬性是否可枚舉):spa
let anotherObject = { a:2 }; // 建立一個關聯到 anotherObject 的對象 let myObject = Object.create( anotherObject ); for (let k in myObject) { console.log("found: " + k); } // found: a ("a" in myObject); // true
所以,當你經過各類語法進行屬性查找時都會查找 [[Prototype]]鏈,直到找到屬性或者查找完整條原型鏈。prototype
全部普通對象的[[prototype]]鏈指向內置的Object.Prototype。因爲全部普通對象的[[prototype]]鏈源於Object.Prototype,因此它含有JavaScript中許多通用的功能。code
給一個對象設置屬性並不只僅是添加一個屬性或者修改屬性值,它包含如下過程:對象
myObject.foo = 'bar';
若是 myObject 對象中包含名爲 foo 的普通數據訪問屬性,這條賦值語句只會修改已有的屬性值
若是 foo 不是直接存在於 myObject 中,[[Prototype]] 鏈就會被遍歷,相似 [[Get]] 操做。
若是原型鏈上找不到 foo,foo 就會被直接添加到 myObject 上。然而,若是 foo 存在於原型鏈上層,賦值語句 myObject.foo = "bar" 的行爲就會有些不一樣。
若是屬性名 foo 既出如今 myObject 中也出如今 myObject 的 [[Prototype]] 鏈上層,那麼就會發生屏蔽。myObject 中包含的 foo 屬性會屏蔽原型鏈上層的全部 foo 屬性,由於myObject.foo 老是會選擇原型鏈中最底層的 foo 屬性。blog
屏蔽比咱們想象中更加複雜。下面咱們分析一下若是 foo 不直接存在於 myObject 中而是存在於原型鏈上層時 myObject.foo = "bar" 會出現的三種狀況:繼承
function Foo() { // ... } Foo.prototype.constructor === Foo; // true var a = new Foo(); a.constructor === Foo; // true
Foo.prototype 默認(在代碼中第一行聲明時!)有一個公有而且不可枚舉的屬性 .constructor,這個屬性引用的是對象關聯的函數 Foo。此外,咱們能夠看到經過「構造函數」調用 new Foo() 建立的對象也有一個 .constructor 屬性,指向「建立這個對象的函數」
function NothingSpecial() { console.log( "Don't mind me!" ); } var a = new NothingSpecial();//Don't mind me! let b = NothingSpecial();//Don't mind me! console.log(a)//NothingSpecial{} console.log(b)//undefined
NothingSpecial 實際上是一個普通函數,但經過使用new調用的時候,它就會構造出一個對象而且賦給 a。這裏的調用只是構造函數調用,可是NothingSpecial自己並非一個構造函數。
(1)instanceof:
function Foo() { // ... } var a = new Foo(); console.log(a instanceof Foo) //true
instanceof 操做符的左操做數是一個普通的對象,右操做數是一個函數。instanceof 回答的問題是:在 a 的整條 [[Prototype]] 鏈中是否有指向 Foo.prototype 的對象?惋惜,這個方法只能處理對象(a)和函數(帶 .prototype 引用的 Foo)之間的關係。若是你想判斷兩個對象(好比 a 和 b)之間是否經過 [[Prototype]] 鏈關聯,只用 instanceof沒法實現。
(2)isPrototypeOf:
let a = { x:1 } let b = Object.create(a); let c = { y:2 }; console.log(a.isPrototypeOf(b));//true console.log(c.isPrototypeOf(b));//false
isPrototypeOf是用來判斷指定對象a是否存在於另外一個對象b的原型鏈中,是則返回true,不然返回false。
(3)Object.getPrototypeOf(對象):
function Foo(){ //... } let a = new Foo(); let b = { x:1 } console.log(Object.getPrototypeOf(a) == Foo.prototype);//true console.log(Object.getPrototypeOf(b) == Foo.prototype);//false
Object.getPrototypeOf(對象)能夠獲取對象的[[prototype]]鏈
(4) proto :
function Foo(){ //... } let a = new Foo(); console.log(a.__proto__); console.log(Foo.prototype); console.log(Foo.prototype.__proto__); console.log(Object.prototype); console.log(Object.prototype.__proto__);
輸出結果:
function Foo(){ //... } let a = new Foo(); let c = { x:1 } console.log(a.__proto__ === Object.prototype);//false console.log(a.__proto__ === Foo.prototype);//true console.log(Foo.prototype.__proto__ === Object.prototype);//true console.log(c.__proto__ === Object.prototype);//true
proto 返回對象上一層[[prototype]]原型對象
若是要訪問對象中並不存在的一個屬性,[[Get]] 操做就會查找對象內部[[Prototype]] 關聯的對象。這個關聯關係實際上定義了一條「原型鏈」(有點像嵌套的做用域鏈),在查找屬性時會對它進行遍歷。
全部普通對象都有內置的 Object.prototype,指向原型鏈的頂端(好比說全局做用域),若是在原型鏈中找不到指定的屬性就會中止。toString()、valueOf() 和其餘一些通用的功能都存在於 Object.prototype 對象上,所以語言中全部的對象均可以使用它們。
關聯兩個對象最經常使用的方法是使用 new 關鍵詞進行函數調用中會建立一個關聯其餘對象的新對象。使用 new 調用函數時會把新對象的 .prototype 屬性關聯到「其餘對象」。帶 new 的函數調用一般被稱爲「構造函數調用」,儘管它們實際上和傳統面向類語言中的類構造函數不同。
雖然這些 JavaScript 機制和傳統面向類語言中的「類初始化」和「類繼承」很類似,可是 JavaScript 中的機制有一個核心區別,那就是不會進行復制,對象之間是經過內部的[[Prototype]] 鏈關聯的。
出於各類緣由,以「繼承」結尾的術語(包括「原型繼承」)和其餘面向對象的術語都沒法幫助你理解 JavaScript 的真實機制(不只僅是限制咱們的思惟模式)。相比之下,「委託」是一個更合適的術語,由於對象之間的關係不是複製而是委託。