初探js中的對象原型(Prototype)

在JavaScript中,萬物皆對象。每一個對象都有一個特殊的內部屬性,[[Prototype]](原型)。它是對於其餘對象的引用,也就是說它關聯到別的對象(若是它不爲空)。函數

在平常中,咱們除了常常聽到原型這個詞以外,還常常會聽到原型鏈這個詞,那麼這兩個詞到底有什麼關係呢,下面咱們就來探究一下。this

那首先咱們來看一下[[Prototype]]這個特殊的內部屬性有什麼用呢?咱們先來說一個關於屬性訪問的一些知識。看下面的代碼:spa

var myObject = {
    a:1;
};
myObject.a; //1

在這裏,咱們能夠清楚的看到Object.a是一個屬性訪問(訪問了Object的a屬性),那麼深刻一下,上面的代碼實際上在Object上執行了一個[[Get]]操做。對一個對象執行默認的[[Get]]操做,會首先檢查對象,尋找請求的屬性,若是找到,就返回相應的值。若是沒找到,這個時候對象的[[Prototype]]屬性以及它的[[Prototype]]鏈就須要派上用場了。prototype

先看代碼:code

var newObject = {
    a:1;
};
var myObject = Object.create(newObject);
myObject.a; //1

首先咱們能夠看到在上面的代碼中,a這個屬性是不存在myObject這個對象裏的,可是最後咱們仍是獲得了a這個屬性的值。對象

var myObject = Object.create(newObject);這行代碼就是關鍵了。它建立了一個對象myObject關聯到newObject。也就是說myObject的[[Prototype]]屬性關聯到了newObject這個對象。[[Get]]操做在對象自己沒找到須要的屬性後,便順着這個關聯去newObject這個對象裏進行查找。
因此在對屬性進行查詢的時候,就會順着這個一層又一層的關聯,也就是所謂的[[Prototype]]鏈,對須要的屬性進行查找。在[[Prototype]]鏈找到了就返回屬性值或者在鏈條的末尾都沒找到就返回undefined。那麼鏈條的末尾是什麼呢?每一個普通的[[Prototype]]鏈的最頂端,是內建的Object.prototype,也就是null。blog

開頭講到,[[Prototype]]這個屬性是把一個對象關聯到另外一個對象。那麼這個關聯有什麼用呢?
常見的用法那就是所謂的原型繼承委託了。繼承

咱們經過代碼來理解一下:圖片

function Foo(a) {
    this.a = a;
}
Foo.prototype.myA = function() {
    return this.a;
};

function Bar(a,b) {
    Foo.call(this,a);
    this.b = b;
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.myB = function() {
    return this.b;
};

var Baz = new Bar( "1", "obj 1" );

Baz.myA(); // "1"
Baz.myB(); // "obj 1"

在上面的代碼中,最重要的部分就是Bar.prototype = Object.create(Foo.prototype);。這個代碼應該會比較眼熟,由於在上面的例子中咱們也用到了類似的代碼,而且解釋說是把一個對象關聯到另外一個對象上。那麼這裏咱們是把新建立的Bar對象的內部的[[Prototype]]連接到了指定的對象Foo.prototypeip

Bar和Foo在這裏既是對象也是函數。函數有一個性質:全部的函數默認都會獲得一個能夠指向任何對象的屬性prototype

圖片描述

指向的對象每每被稱爲「函數的原型」。每一個由調用new Foo()而建立的對象將最終被[[Prototype]]連接到所謂的「函數的原型」。
看段代碼來深刻了解一下:

function Foo(a){
    this.a = a;
}
var bar = new Foo(2);
bar.a; //2
Object.getPrototypeOf(bar) === Foo.prototype; //true

那麼爲何調用new Foo()建立的對象能夠連接到函數原型呢?這就跟它的實現有關了。
當函數前面加new調用時,會有如下事情完成:

  • 一個全新的對象被建立
  • 這個新的對象會被接入到原型鏈
  • 這個新的對象被設置爲函數調用的this綁定
  • 除非函數有返回對象,不然這個被new調用的函數將自動返回這個新的對象

主要咱們要看的是第二點,其餘的會在this綁定的文章中講到。
因此在上面的例子中,調用new Foo()建立bar的時候,bar會獲得一個內部的[[Prototype]]連接,連接到Foo.prototype所指向的對象。

在「原型繼承」的例子代碼中var Baz = new Bar( "1", "obj 1" );也是使用了new來建立對象,從而使baz對象連接到bar.prototype上.

到目前爲止,咱們提到了2種方法來建立新對象並與其餘對象關聯:

  • Object.create()
  • 函數前面加new調用

表面上看好像均可以達到咱們的目的,可是第二種方法可能會有意想不到的反作用產生。假如說Foo()這個函數還能夠改變狀態或者向this添加數據屬性等,那麼在對象在被建立的時候就會有這些咱們可能自己並不須要的東西。

最後用一張圖來總結一下原型繼承:(圖來源:You-Dont-Know-JS)
圖片描述

從圖中能夠看出,箭頭由右至左,由下至上。而[[Prototype]]機制,就是一種存在於對象上的內部連接,指向另外一個對象。

事實上,「原型繼承」這個詞很容易讓人產生誤會,由於JavaScript中沒有類,也就沒有繼承。實際上,Bar與Foo不能說是繼承的關係,用準確點的術語來講,是委託。因此接下來咱們就要來了解一下委託了。
很簡單,委託實際上就是原型鏈。好比說新建對象a連接到對象b,a能夠使用b的方法,也就是a委託了b(來實現它想要實現的效果)。這樣的話直接用Object.create就能夠了,不須要再調用new函數了。var a = Object.create(a);

相關文章
相關標籤/搜索