理解原型實際上是理解原型鏈

前言

原型和原型鏈,說是兩個詞,其實理解一個就能夠了。這兩個概念是同時存在的,不可能拋開一個去談論另一個,或者說這兩個概念結合在一塊兒纔會發揮做用,甚至原型的存在是由於有原型鏈的存在,不在原型鏈上的原型只能稱之爲對象。javascript

原型鏈

先來講說原型鏈是個什麼東東,提及鏈咱們如今腦海中描繪一下本身對鏈這個字的第一反應是什麼,是社會我大哥的大金鍊? 前端

是二哈的大狗鏈?
仍是數據結構鏈表?

皮一下,下面咱們正經說原型鏈,原型鏈從本質上來說應該是個鏈表結構,也就是和上面的單鏈表有點像,咱們把上圖中的next換成__proto__屬性,data換成鍵值對集合,這樣常常在控制檯輸出對象的同窗會不會有點熟悉的感受?java

舉個栗子

下面舉個庸俗的例子,有一個對象人,有move和sleep屬性,人中又有男人具備sex屬性,男人中又有一類人程序員具備code和hair(其值爲less)屬性,如今咱們想用一個對象來表達程序員,那麼這個對象應該同時具有人,男人,程序員的屬性,咱們用原型鏈來表達他們,就像這樣程序員

原型鏈具有的特徵是可以從下往上查找屬性,利於當我在要programmer對象中讀取sex屬性時,瀏覽器引擎會先在programmer對象中查找該屬性,若是未查找到,那麼經過其__proto__找到man對象,在man對象中去查找,在man對象中查找到了sex屬性,並獲取其值‘man’將其返回,就完成了一次屬性查找。同理若是要經過programmer獲取move,也是這樣層層查找自身屬性並經過__proto__往上查找。設計模式

程序員的睡覺時間與通常人不一樣,所以須要定義本身的sleep方法,直接在programmer對象上設置sleep屬性,那麼programmer對象就具備了本身的sleep屬性,當經過程序員獲取sleep屬性時獲取到的就是本身定義的sleep屬性,也就是說一樣都是person,此刻的programmer的sleep已不是peroson的sleep。瀏覽器

原型鏈的特徵

經過上上面的例子,咱們不可貴出原型鏈具備的兩個基本特徵:數據結構

  1. 查找屬性時可順鏈向上查找
  2. 設置屬性時只能設置當前對象的屬性,而不會影響其上層鏈上的對象屬性

第一點特徵經常被人們稱爲繼承,可是應該不能算是真正的繼承,只能說在表現上與繼承無異。真正意義上的繼承是你從某處學會了某項能力,就算只有你一我的的時候你也是具有這項能力的,可是咱們的原型鏈更應該是一個委託鏈,你能夠經過這個委託鏈獲取這個鏈上自你以後全部對象的能力,若是這個鏈發生變化你可能會失去某項能力。繼承是對象自己具備這個能力或者特性,而原型委託是你及你身後的委託鏈具有這個能力。固然這對於對象的使用者咱們來講是無所謂的,咱們沒必要過度糾結究竟是繼承仍是委託,可是瞭解事情的本質也是一件不錯的事。less

其實原型鏈具有這兩點特徵其實是很天然而然的,這樣的表現形式並無太多刻意的違背正常邏輯的人爲規定,咱們只需稍微思考其在實際中的做用就能理解。函數

__proto__和prototype的關係

原型的英文是什麼來着,嗯,prototype,只要說到原型就會被人們提起的一個詞。那麼它到底和原型有沒有關係呢?這裏我要說這個詞雖然是原型的意思,其實它和原型並無什麼關係,騷年們之後不要直接在對象上去a.prototype了,這樣你大多數狀況下獲得的只會是undefined(在函數對象上能夠獲取到值)。能在對象上直接獲取其原型的是__proto__,你a.__proto__多數通常都能取到值,這個屬性記錄了該對象的原型對象地址。ui

prototype

這個詞其實和原型鏈是有關係的,和原型真的一點關係沒有,其做用是用來指定你使用new關鍵字調用函數的時候生成實例對象的原型(這個原型後面可能還藏着一條原型鏈)的。下面上代碼

var person = {
  move: function() {
    console.log('moving')
  },
  sleep: function() {
    console.log('sleeping')
  }
};

function Man() {
  this.sex = 'man'
}

// 爲new Man()獲得的對象指定原型對象person
Man.prototype = person;

function Programmer() {
  this.hair = 'less';
  this.code = function() {
    console.log('coding')
  }
}

// 爲new Programmer()獲得的對象指定原型對象new Man()
Programmer.prototype = new Man();

var programmer = new Programmer();
console.log(programmer);
複製代碼

從上面的代碼的運行結果中咱們不難看出,prototype的做用只是在特定場景下獲得的對象的原型(還有其餘多種指定對象原型的方式,下面另開小節說明),且這裏指定的不只僅是原型,當指定man爲programmer的原型時,同時也意味着man的原型person及person的原型Object這一整個鏈都被指定給了programmer。

__proto__

上面的運行結果中咱們能夠看到,在每一個對象上都有一個屬性__proto__,這個屬性不是咱們指定的,並且這只是在大多數瀏覽器中這個屬性名是__proto__,這個屬性名的做用就是記錄對象的原型指向。雖然不必定每一個瀏覽器中都是這個屬性名,可是相同的是他們必然都有一個屬性用來記錄對象的原型。當咱們要獲取一個對象的原型時應該使用ES的標準API: Object.getPrototypeOf()或者Reflect.getPrototypeOf()(ES6),來獲取。

原型鏈關係圖

話說原本只是隨便畫畫的,結果就成了你上面看到那個樣子,讓咱們你們一塊兒來找茬,發現有哪一個等式不成立的歡迎在評論區打臉。另表達下我的的對JS中的對象起點觀念,我認爲是Object.prototype指向的這個對象,不認爲是null,不接受反駁(傲嬌臉)。關於這一點,這裏解釋一下,在JS中全部對象的原型鏈追到最後應該都是Object.prototype指向的對象(經過使用下面的指定原型對象爲null的方式的對象除外),上圖中所表現的想代表的是具有實際使用意義的原型鏈起點。

Object.prototype.__proto__的值是null也就是代表Object.prototype對象是存在這個屬性的,由於它不是undefined,可是這個值應該被理解爲一個原型鏈的結束符號更爲準一些,使用這個符號是爲了可以更好的實現JS引擎,而不是語言具有的標準特性。

指定對象原型的幾種方式

總結了下指定對象的原型的幾種方法,大致可分爲非標準操做,標準API操做,特定場景操做。爲了方便舉例,咱們設定一個場景,對象a有name屬性,其值爲a,對象b有color屬性,其值爲red,如今要求將a指定爲b的原型。

非標準操做

這個是最簡單粗暴的方式,直接設置對象的__proto__屬性,像下面這樣

var a = {name: 'a'};
var b = {id: 'b'};
console.log('a的原型是Object.prototype', Object.getPrototypeOf(a) === Object.prototype);    //true
a.__proto__ = b
console.log('a的原型是b', Object.getPrototypeOf(a) === b);  //true
複製代碼

代碼傳送門

這種方式雖然很簡單,可是通常不建議在生產代碼中使用,這種寫法存在兼容性上的問題,這個沒有在標準中規定的屬性只是靠各瀏覽器廠商之間的默契維持,兼容性可想而知。其次這種方式在代碼的可維護性上不是很好,畢竟不是誰都知道這個屬性__proto__(雖然以爲搞前端的同窗應該都知道)。

標準API操做

下面來介紹幾個指定對象的API(水字數)。

Object.setPrototypeOf

這個是Object對象的一個靜態方法,使用方式以下

var a = {name: 'a'};
var b = {id: 'b'};
console.log('a的原型是Object.prototype', Object.getPrototypeOf(a) === Object.prototype);    //true
Object.setPrototypeOf(b);
console.log('a的原型是b', Object.getPrototypeOf(a) === b);  //true
複製代碼

這個方法使用簡單,兼容性好,指定對象的原型首推使用這個方法。

Reflect.setPrototypeOf

此API的方式同上,沒什麼好說的,只是這個ES6標準中提供的方法。按照阮老師的說法,Reflect對象應該會將Object上定義的一些對象操做方法都接收過來。

Object.create

Object.create()方法建立一個新對象,使用現有的對象來提供新建立的對象的__proto__。 下面上代碼

var b = {id: 'b'};
var c = Object.create(b, {
    name: {
    	value: 'a'     
    }
});
console.log('a的原型是b', Object.getPrototypeOf(c) === b);  //true
複製代碼

使用Object.create()方法會獲得一個指定屬性的新對象,這個方法的第一個參數能夠指定新獲得對象的原型,第二個參數能夠指定對象屬性值等。

若是你想獲得一個純淨的的對象(沒有原型),能夠在上面三個API使用時指定原型對象那個參數傳入null

特定場景操做

將使用new關鍵詞調用函數建立實例對象,經過指定函數的prototype屬性來指定對象的方式放在特定場景操做,是由於這種方式不具有上面幾種方式的靈活性,不能隨時隨地的修改對象的原型,使用起來也比較麻煩,怎麼用你們應該都懂,這裏就很少說了。。

結論

一般咱們在談論原型的時候,應該都是在談論這種設計模式,這應該是一種思想,一種解決問題的方式,咱們對它的理解不該該僅僅停留在對機制的理解上。這種模式的優勢在於你只須要在原型上的添加某個屬性,指向該原型的全部對象都會具備這個屬性,而不用一個一個的去給這些對象添加這個屬性。

相關文章
相關標籤/搜索