本文主要對JS原型相關的問題作一些總結。從一個簡單的例子提及:javascript
function Foo() {
...
}
Foo.prototype.constructor === Foo; // true
let foo = new Foo();
foo.constructor === Foo; // true
複製代碼
當聲明一個函數的時候,默認會給函數的prototype
添加一個.constructor
屬性,該屬性指向該函數。當調用foo.constructor
的時候能夠看到通用指向Foo
函數,是否是foo
實例也一樣有constructor
屬性?java
答案是否認的!這裏foo
是經過原型找到該屬性的,即foo.__proto__.constructor
。那foo.__proto__
又是指向哪裏呢?git
其實foo.__proto__
就是指向了Foo.prototype
:foo.__proto__ === Foo.prototype
。github
咱們知道,在對象的原型上添加方法,則實例能夠經過原型找到對應的方法,譬如:瀏覽器
Foo.prototype.sayHello = function() {
return "hello";
}
foo.sayHello(); // hello
複製代碼
這裏foo
經過__proto__.sayHello
, 將方法調用委託給了Foo.prototype
對象,調用Foo.prototype
對象上的sayHello
方法。一樣以前將到的foo.constructor
也是委託給了Foo.prototype
對象。函數
若是咱們手動修改了Foo.prototype
對象, 如:ui
function Foo() {
...
}
Foo.prototype = {
// 重置該對象爲空後沒有constructor屬性
}
let foo = new Foo();
foo.constructor === Foo; // false
複製代碼
此時foo.constructor
時,即foo.__proto__
是指向了新的Foo.prototype={}
的,天然是找不到constructor
屬性了!說明無心間對prototype
的修改會致使原型鏈的斷裂。this
__proto__
與 prototype__proto__
與prototype
都是維護原型鏈重要的部分,但這兩種有什麼區別呢?spa
__proto__
: JS每一個對象內部都隱含了一個__proto__
屬性,用來指向它的原型對象,即xxx.prototype
。例如對象a:let a = {name: "example"}
prototype
prototype
: 與__proto__
不一樣的是,這個屬性只有函數纔有。而且添加到prototype
上的屬性和方法在全部實例中都是共享的。譬如Foo.prototype
,能夠看到Foo.prototype
中也有__proto__
屬性,指向它的原型對象。
經過原型很容易實現對象間的繼承,JS中繼承是經過委託機制實現的。最經常使用的一種方式是經過Object.create(..)
方法。
function Foo() {
this.name = "Foo";
}
Foo.prototype.sayName = function () {
return "This is " + this.name;
}
function Bar() {
this.name = "Bar";
}
// 讓Bar繼承Foo
Bar.prototype = Object.create(Foo.prototype);
let bar = new Bar();
bar.sayName(); // This is Bar
複製代碼
經過Object.create(..)
方法,讓Bar
的原型鏈指向Foo
的原型實現繼承效果。如圖所示:
爲了詳細瞭解繼承效果,咱們也能夠本身動手實現。本身實現一個生成子類的方法subClassOf()
。
function subClassOf(o) {
function F() {};
F.prototype = o;
return new F(); // new F() 產生一個新對象,該對象內部__proto__關聯F.prototype.
}
Bar.prototype = subClassOf(Foo.prototype);
let bar = new Bar();
bar.sayName(); // This is Bar
複製代碼
原理其實很簡單,即經過一箇中間函數F
做爲中間橋樑,鏈接Bar
和Foo
的原型鏈,以下圖所示:
這裏注意的是,若是寫成Bar.prototype = subClassof(Foo)
這樣的話,構造的原型鏈是不成立的,如圖:
這裏F.prototype = Foo
,並無指向Foo.prototype
, 致使原型鏈斷裂,當調用bar.sayName()
時,會報找不到對應方法,由於該方法是在Foo.prototype
上的。
JS Object Layout
這張圖是該篇博文中的一張JS原型關係圖,這張圖很清楚的顯示了原型中的各類關係。
經過這副圖能夠總結如下幾個要點:
Foo()
函數和其實例f1
均可以訪問到Foo.prototype
。所以二者均可以訪問原型鏈上的方法。Foo.prototype
的constructor
是指向自身函數Foo()
的。constructor
存在的意義是爲了讓實例對象f1
能夠藉助constructor
訪問定義在Foo()
函數中的屬性和方法。注意如下這種狀況:
Bar.prototype = new Foo();
複製代碼
這種狀況下Bar.prototype.constructor
的指向是錯誤的,此時是指向的Foo
函數,因此必需要修正constructor
:Bar.prototype.constructor = Bar
。
獲取一個對象的原型鏈,能夠直接經過__proto__
獲取,如 bar.__proto__
。但這是一種非標準的方法,並非全部瀏覽器都支持。ES5中的標準方法是Object.getPrototypeOf()
。
Bar.prototype = new Foo();
// 修正前
Bar.prototype.constructor === Bar; // false;
// 修正後
Bar.prototype.constructor = Bar;
Bar.prototype.constructor === Bar; // true;
複製代碼
Object.getPrototypeOf(bar) === bar.__proto__ === Bar.prototype
複製代碼
若是要使兩個對象關聯起來,好比讓對象bar關聯對象foo, 一樣使用Object.create()
方法
function Foo() {
this.name = "Foo";
this.something = function() {
console.log("This is something");
}
}
Foo.prototype.sayName = function () {
console.log("This is " + this.name);
}
// bar 關聯 foo
let bar = Object.create(foo);
複製代碼
這樣bar
能夠調用foo
的something()
方法和sayName()
方法。
這裏須要注意的是,bar
對象直接關聯到foo
對象,與上述講的繼承關係又不同了,繼承時是經過Bar.prototype = Object.create(Foo.prototype)
將原型鏈進行關聯的。能夠看到:
Object.getPrototypeOf(bar) === Bar.prototype; // false
Object.getPrototypeOf(bar) === foo; // true
複製代碼
這裏僅僅是兩個對象的關聯!所以不只能夠調用Foo.prototype原型上的方法,也能夠調用foo對象上的方法。如圖: