JavaScript 對象原型以及原型鏈

對象原型&原型鏈

1.原型 [[Prototype]]

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

Object.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" 會出現的三種狀況:繼承

  1. 若是在 [[Prototype]] 鏈上層存在名爲 foo 的普通數據訪問屬性而且沒有被標記爲只讀(writable:false),那就會直接在 myObject 中添加一個名爲 foo 的新屬性,它是屏蔽屬性。
  2. 若是在 [[Prototype]] 鏈上層存在 foo,可是它被標記爲只讀(writable:false),那麼沒法修改已有屬性或者在 myObject 上建立屏蔽屬性。若是運行在嚴格模式下,代碼會拋出一個錯誤。不然,這條賦值語句會被忽略。總之,不會發生屏蔽。
  3. 若是在 [[Prototype]] 鏈上層存在 foo 而且它是一個 setter,那就必定會調用這個 setter。foo 不會被添加到(或者說屏蔽於)myObject,也不會從新定義 foo 這個 setter。

2. 構造函數

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自己並非一個構造函數。

3. 檢查原型關係

(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__);

輸出結果:

11_50_06__03_17_2020.jpg

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]]原型對象

4. 總結

若是要訪問對象中並不存在的一個屬性,[[Get]] 操做就會查找對象內部[[Prototype]] 關聯的對象。這個關聯關係實際上定義了一條「原型鏈」(有點像嵌套的做用域鏈),在查找屬性時會對它進行遍歷。

全部普通對象都有內置的 Object.prototype,指向原型鏈的頂端(好比說全局做用域),若是在原型鏈中找不到指定的屬性就會中止。toString()、valueOf() 和其餘一些通用的功能都存在於 Object.prototype 對象上,所以語言中全部的對象均可以使用它們。

關聯兩個對象最經常使用的方法是使用 new 關鍵詞進行函數調用中會建立一個關聯其餘對象的新對象。使用 new 調用函數時會把新對象的 .prototype 屬性關聯到「其餘對象」。帶 new 的函數調用一般被稱爲「構造函數調用」,儘管它們實際上和傳統面向類語言中的類構造函數不同。

雖然這些 JavaScript 機制和傳統面向類語言中的「類初始化」和「類繼承」很類似,可是 JavaScript 中的機制有一個核心區別,那就是不會進行復制,對象之間是經過內部的[[Prototype]] 鏈關聯的。

出於各類緣由,以「繼承」結尾的術語(包括「原型繼承」)和其餘面向對象的術語都沒法幫助你理解 JavaScript 的真實機制(不只僅是限制咱們的思惟模式)。相比之下,「委託」是一個更合適的術語,由於對象之間的關係不是複製而是委託。

相關文章
相關標籤/搜索