js中的對象有一個特殊的[[Prototype]]內置屬性,即對於其餘對象的引用。幾乎全部的對象在建立時[[Prototype]]屬性都會被賦予一個非空值。對象的[[Prototype]]連接能夠爲空,但不多見。瀏覽器
var myObject = {
a:2
};
myObject.a; // 2
複製代碼
當你試圖引用對象的屬性時會觸發[[Get]]操做,如myObject.a。對於默認的[[Get]]操做來講,第一步是檢查對象自己是否有這個屬性,如有就使用。bash
若是a不在myObject中,就須要使用對象的[[Prototype]]鏈了。函數
對於默認的[[Get]]操做來講,若是沒法在對象自己找到須要的屬性,就會繼續訪問[[Prototype]]鏈:性能
var anotherObject = {
a:2
};
// 建立一個關聯到anotherObject的對象
var myObject = Object.create(anotherObject);
myObject.a; // 2
複製代碼
myObject對象的[[Prototype]]關聯到了anotherObject。myObject.a並不存在,但屬性能在anotherObject中找到2。ui
若是anotherObject也找不到a而且[[Prototype]]不爲空,則繼續查找下去。this
這個過程會持續到找到匹配的屬性名或查找完整條[[Prototype]]鏈。如果後者[[Get]]操做返回undefined。spa
使用for..in遍歷對象時原理和查找[[Prototype]]鏈相似,任何能夠經過原型鏈訪問到的屬性都會被枚舉。使用in操做符來檢查屬性在對象中是否存在,一樣會查找對象的整條原型鏈(不管對象是否可枚舉):prototype
var anotherObject = {
a:2
};
// 建立一個關聯到anotherObject的對象
var myObject = Object.create(anotherObject);
for (var k in myObject) {
console.log("found: " + k);
}
// found: a
("a" in myObject); // true
複製代碼
全部普通的[[Prototype]]鏈最終對吼指向內置的Object.prototype。因爲全部「普通」(內置)對象都「源於」Object.prototype,因此它包含了js中許多通用功能日誌
myObject.foo = "far";
複製代碼
若是myObject對象包含名爲foo的普通數據訪問屬性,這條賦值語句只會修改已有的屬性值。code
若是foo不是直接存在於myObject中[[Prototype]]鏈就會被遍歷,相似[[Get]]操做。若是原型鏈上找不到foo,foo就會被直接添加到myObject上。
若是foo存在於原型鏈上層,賦值語句myObject.foo = "bar"的行爲就會有些不一樣。
若是屬性名foo既出如今myObject的[[Prototype]]鏈上層,則會發橫屏蔽。myObject中包含的foo屬性會屏蔽原型鏈上層的全部foo屬性,由於myObject.foo老是會選擇原型鏈中最底層的foo屬性。
下面直接分析若是foo不直接存在於myObject中而是存在於原型鏈上層時myObject.foo = "bar"會出現的三種狀況。
一、若是在[[Prototype]]鏈上層存在名爲foo的普通數據訪問屬性而且沒有被標記爲只讀(writable:false)則直接在myObject中添加一個名爲foo的屬性,它是屏蔽屬性。
二、若是在[[Prototype]]鏈上層存在foo,但被標記爲只讀(writable:false),則沒法需改已有屬性或在myObject上建立屏蔽屬性。若是在嚴格模式下,代碼則會跑出一個錯誤。不然,這條賦值語句會被忽略。總之,不會發生屏蔽。
三、若是在[[Prototype]]鏈上層存在foo而且它是一個setter,則會調用這個setter。foo不會被添加到或屏蔽於myObject,也不會從新定義foo這個setter。
若是但願在第二種第三種狀況下也屏蔽foo,就不能用=操做符來賦值,而使用Object.defineProperty(..)來向myObject添加foo。
一般應當儘可能避免使用屏蔽
有些狀況下會隱式產生屏蔽:
var anotherObject = {
a:2
};
var myObject = Object.create(anotherObject);
anotherObject.a; // 2
myObject.a; // 2
anotherObject.hasOwnProperty("a"); // true
myObject.a++; // 隱式屏蔽
anotherObject.a; // 2
myObject.a; // 3
myObject.hasOwnProperty("a"); // true
複製代碼
++操做首先會經過[[Prototype]]查找屬性a並從anotherObject.a獲取當前屬性值2,而後給這個值加1,接着用[[Put]]將值3賦給myObject中新建的屏蔽屬性a
全部的函數默認都會擁有一個名爲prototype的共有而且不可枚舉的屬性,他會指向另外一個對象:
function Foo() {
// ...
}
Foo.prototype; // {}
複製代碼
這個對象一般被稱爲Foo的原型。這個對象是在調用new Foo()時建立的,最後會被關聯到「Foo.prototype」對象上
function Foo() {
// ...
}
var a = new Foo();
Object.getPrototypeOf(a) === Foo.prototype; // true
複製代碼
調用new Foo()時會建立a,其中一步就是將a內部的[[Prototype]]連接到Foo.prototype所指向的對象。
function Foo() {
// ...
}
// constructor公有不可枚舉的屬性
Foo.prototype.constructor === Foo; // true
var a = new Foo();
a.constructor === Foo; // true
複製代碼
a自己沒有constructor屬性,雖然a.constructor確實指向Foo函數,可是這個屬性並非表示a由Foo「構造」
實際上.constructor引用一樣被委託給了Foo.prototype,而Foo.prototype.constructor默認指向Foo。
a.constructor只是經過默認的[[Prototype]]委託指向Foo,這和「構造」毫無關係。
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; //建立一個新原型對象
var a1 = new Foo();
a1.constructor === Foo; // false
a1.constructor === Object; // true
複製代碼
a1並無.constructor屬性,因此它會委託[[Prototype]]鏈上的Foo.prototype。但這個對象也沒有.constructor屬性(不過默認的Foo.prototype對象有這個屬性),因此它會繼續委託,此次會委託給委託鏈頂端的Object.prototype。這個對象有.constructor屬性,指向內置的Object(..)函數
固然你能夠給Foo.prototype添加一個.constructor屬性,不過須要手動添加一個符合正常行爲不可枚舉的屬性。
function Foo() { /* .. */ }
Foo.prototype = { /* .. */ }; //建立一個新原型對象
// 須要在Foo.prototype上「修復」丟失的.constructor屬性
// 新對象屬性起到Foo.prototype的做用
// 關於defineProperty(..)
Object.defineProperty( Foo.prototype, "constructor", {
enumerable: false,
writable: true,
configurable: true,
value: Foo // 讓.constructor指向Foo
})
複製代碼
.constructor並不表示被構造,也不是一個不可變屬性。他是不可枚舉的,但值是可寫的
1)構造函數仍是調用
function NothingSpecial() {
console.log("Don't mind me!");
}
var a = new NothingSpecial();
// Don't mind me! a; // {} 複製代碼
NothingSpecial只是一個普通的函數,但使用new調用時,就會構造一個對象並賦值給a。這個調用時一個構造函數調用,但NothingSpecial自己不是構造函數
函數不是構造函數,當且僅當使用new時,函數調用會變成「構造函數調用」
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
}
var a = new Foo("a");
var b = new Foo("b");
a.myName(); // "a"
b.myName(); // "b"
複製代碼
這段diamante展現了另外兩種「面向類」的技巧:
一、this.name = name給每一個對象,即a,b都添加了.name屬性
二、Foo.prototype.myName = ...會給Foo.prototype對象添加一個屬性(函數)
在建立過程當中,a、b的內部[[Prototype]]都會關聯到Foo.prototype上。當a、b中沒法找到myName時,它會經過委託在Foo.prototype上找到
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
}
function Bar(name, label) {
Foo.call(this, name);
}
// 建立一個新的Bar.prototype對象並關聯到Foo.prototype
Bar.prototype = Object.create(Foo.prototype); // 核心部分
// 注意如今沒有Bar.prototype.constructor了
// 若你須要則手動修復
Bar.prototype.myLabel = function() {
return this.label;
}
var a = new Bar("a", "obj a");
a.myName(); // "a"
a.myLabel(); // 'obj a'
複製代碼
建立一個新對象並把它關聯到咱們但願的對象上有兩種常見的作法:
// 和你想要的機制不同
Bar.prototype = Foo.prototype;
複製代碼
此代碼讓Bar.prototype直接引用Foo.prototype對象。所以當你執行類型Bar.prototype.myLabel = ... 的賦值語句時會修改Foo.prototype對象自己
// 基本上知足你的需求,但可能會產生一些反作用
Bar.prototype = new Foo();
複製代碼
此語句會建立一個關聯到Bar.prototype的新對象。但使用了Foo(..)的構造函數調用。若是函數Foo有一些反作用(如:寫日誌、修改轉態等)就會影響到Bar()的「後代」,後果不堪設想
要建立一個合適的關聯對象,最好使用Object.create(..)而不是具備徐做用的Foo(..)。但這須要建立一個新對象而後把舊對象拋棄掉,不能直接修改已有的默認對象。
// ES6以前須要拋棄默認的Bar.prototype
Bar.prototype = Object.create(Foo.prototype);
// ES6能夠直接修改現有的Bar.prototype
Object.setPrototypeOf(Bar.prototype, Foo.prototype);
複製代碼
檢查「類」關係
內省(反射):檢查一個實例的繼承祖先(js的委託關聯)
function Foo() {
// ...
}
Foo.prototype.blah = ...;
var a = new Foo();
複製代碼
如何經過內省找出a的「祖先」(委託關聯)
1)a instanceof Foo; 在a的整條[[Prototype]]鏈中是否有指向Foo.prototype的對象
缺點:只能處理對象(a)和函數(帶.prototype引用的Foo)之間的關係
若是使用內置的.bind(..)函數來生成一個硬綁定函數的話,該函數沒有.prototype屬性。在這樣的函數上使用instanceof的話目標函數的.prototype會代替硬綁定函數的.prototype。
function isRelated(o1, o2) {
function F() {}
F.prototype = o2;
return o1 instanceof F;
}
var a = {};
var b = Object.create(a);
isRelated(b, a); // true
複製代碼
2)判斷[[Prototype]]反射的方法
Foo.prototype.isPrototypeOf(a); // true
複製代碼
在a的整條[[Prototype]]鏈,中是否出現過Foo.prototype
此方法不須要間接引用函數(Foo),它的.prototype屬性會被自動訪問
// 很是簡單:b是否出如今c的[[Prototype]]鏈中
b.isPrototypeOf(c);
複製代碼
這個方法並不須要使用函數(「類」),它直接使用b和c之間的對象引用來判斷他們的關係。換而言之,語言內置的isPrototypeOf(..)函數就是咱們的isRelatedTo(..)函數
咱們也能夠直接獲取一個對象[[Prototype]]鏈,在ES5中標準方法:
Object.getPrototypeOf(a);
Object.getPrototypeOf(a) === Foo.prototype; // true
a.__proto__ === Foo.prototype; // true 大多數瀏覽器也支持這種方法
複製代碼
.__proto__存在於內置的Object.prototype中(不可枚舉)
Object.defineProperty(Object.prototype, "__proto__", {
get: function() {
return Object.getPrototypeOf(this);
},
set: function(o) {
Object.setPrototypeOf(this, o);
return 0;
}
})
複製代碼
所以,訪問(獲取值)a.__proto__時,其實是調用了a.proto()(調用getter函數)。雖然getter函數存在於Object.prototype對象中,但它的this指向對象a,因此Object.getPrototypeOf(a)結果相同。
.__proto__是可設置屬性,但一般來講不須要修改已有對象的[[Prototype]],。
在特殊狀況下須要設置函數默認.prototype對象的[[Prototype]],讓它引用其餘對象(除了Object.prototype)這樣可避免使用全新對象替換默認對象。此外最好把[[Prototype]]對象關聯看作是隻讀特性,從而增長代碼可讀性
var foo = {
something:function() {
console.log("Tell me sometihng good ...");
}
};
var bar = Object.create(foo);
bar.something(); // Tell me sometihng good ...
複製代碼
Object.create(..)會建立一個新對象(bar)並把它關聯到咱們指定的對象(foo),這樣能夠避免沒必要要的麻煩。
Object.create(..)的polyfill代碼
Object.create(..)是在ES5中新增的函數,因此在ES5以前的環境中要支持這個功能的話就須要使用一段簡單的polyfill代碼。
if (!Object.create) {
Object.create = function(o) {
function F(){}
F.prototype = o;
return new F();
}
}
複製代碼
這段polyfill代碼使用了一個一次性函數F,咱們經過改寫它的.prototype屬性使其指向想要關聯的對象,而後再使用new F()來構造一個新對象進行關聯。