JavaScript的prototype的理解

prototype是JavaScript比較難理解的一部分,繞來繞去的。不理出頭緒來,理解原型的概念是件頭疼的事情。
爲了方便後面的說明,先從對象提及。數組

對象

廣義對象

JavaScript裏任何事物都是對象,無論什麼,都是從Objec衍生出來的。function,array,string,{},都是對象。只是你們的功能各有不一樣。Object就像女媧,JavaScript世界的任何事物,都是它「創造」的。這裏的對象是廣義的泛指的對象,是一切事物的統稱。函數

狹義對象

狹義對象是指通常的對象類型,經過var p = new Object()或者經過var p = {}的方式建立出來的東西。用來表示某一種事物,包含事物的屬性和方法。後面咱們說的對象就是狹義對象,專門指JavaScript裏產生出來的實例對象。其餘的函數、數組等咱們只從它自己的功能角度來看待,不看成對象,當作是執行功能、儲存數據的一種工具。工具

對象的產生

對象的建立

咱們建立一個對象最經常使用的方法時var p = {},這只是JavaScript建立對象的快捷方式,其根本是經過var p = new Object()的方式產生。這種方式是經過構造函數來建立對象的。this

構造函數

構造函數建立對象

如何經過構造函數來建立對象呢?網上有不少資料,這裏簡單敘述一下。有以下構造函數:prototype

function Person(name) {
    this.name = name; // 屬性
    this.run = function() { // 方法
        console.log("I'm running");
    }
}

上面是一個簡單的構造函數。構造函數它首先是一個函數,跟咱們日常寫的函數是同一類。不一樣的是它裏面有一個this指針,指向經過它產生的實例對象。另外,首字母大寫是咱們用以區分構造函數和普通函數的語法習慣,沒有強制規定必定要大寫。但爲了方便理解最好用首字母大寫的命名習慣來命名。指針

var p = new Person();

經過new的方式建立實例。上面變量p就是一個實例對象,它包含了一個name屬性和一個run方法。用new 構造函數建立對象的過程有兩步:code

  1. 在內存中開闢一塊內存地址,用以存放新的實例對象
  2. 實例對象調用構造函數,那麼裏面的this指針指向這個實例,從而爲對象設置了name屬性和run方法。

name屬性和run方法將是實例對象自身的東西,屬於對象自有。至關於你看到的結果是這樣一個對象:對象

p = {
    name: "Pelemy",
    run: functioin() {
        cosole.log("I'm running");
    }
}

實例對象重複建立的問題

按照上面的步驟建立對象,每執行一次 new Person() 就會有一個新的對象產生。繼承

var p1 = new Person(); // 新對象,新的內存空間
var p2 = new Person(); // 新對象,新的內存空間
console.log(p1 === p2); // false
console.log(p1.run === p2.run) // false

每一個對象都有本身name屬性和run方法。這裏有個問題,每一個實例對象run方法的實現都是同樣的,這是一個雷同的方法。雷同的方法每一個人都有一個,這很浪費資源。若是有一個地方共享,同一種類的對象都去共享的地方取就行了,不須要每一個都留一個備份在本身身上。爲了處理這種狀況,prototype產生了。ip

構造函數的prototype屬性

構造函數中this指針設置的屬性和方法將是新實例對象自身擁有的屬性和方法,咱們叫本地屬性和方法。爲了使各個產生的實例對象不重複設置相同的屬性或方法,JavaScript把這部分放到了構造函數的一個地方,這個地方就是構造函數的prototype屬性指向的對象(注意這裏的對象也是前面說的狹義對象)。prototype本意是原型、藍圖,在這裏我認爲把它叫作「引用屬性」來理解更貼切。仍是之前面的例子。

function Person(name) {
    this.name = name; // 定義本地屬性
}
Person.prototype.run = function() { // 定義引用方法
    console.log("I'm running");
}

這裏可能會忽然讓人頭疼。Person.prototype.run忽然多了這麼長一串,連續三個點。咱們一個個看。首先把prototype看成一個屬性,就像咱們常寫一個對象的屬性那樣,好比 car.color, car.speed。這裏也同樣,prototype是構造函數的一個屬性,它的值是一個對象

Person.prototype = {
 // properties and methods
}

這個對象就是未來咱們用來存放共享屬性和方法的。這些屬性和方法能夠被實例對象引用。注意是引用,也就是本身沒有,指向那裏去調用就好了。而後在這個對象裏定義run方法

Person.prototype = {
   // properties and methods
   run: function() {
     console.log("I'm running");
   }
}

固然,咱們這裏只是爲了多定義一個run方法,而不是定義整個prototype的對象(這樣會把這個對象的其餘方法擦掉,只剩下run方法)。因此定義整個引用方法的方式就是
object.run = ... 即
Person.prototype.run = ...
這樣新建立的實例不再用本身定義這個方法,只要從共享對象上引用就行了。舉例:

function Person(name) {
    this.name = name; // 屬性
    this.myfunction = function() {
        console.log("just work for me");
    }
}
Person.prototype.run = function (distance) {
    console.log("I've run " + distance + " meters");
}
var p1 = new Person("Pelemy");
var p2 = new Person("Summmy");
console.log(p1.name); // Pelemy
p1.run(100); // I've run 100 meters
console.log(p2.name); // Summy
p2.run(200); // I've run 200 meters

p1,p2的自己沒有run方法(構造函數裏沒定義),但都能執行到,他們是引用prototype裏的run方法。執行方法時,先在本地查找是否有這個方法,沒有則向上尋找prototype對象是否有,有則執行這個方法。這裏能夠經過hasOwnProperty方法來判斷本地是否有這個方法,沿用上面的例子

console.log(p1.hasOwnProperty("run")); // false
console.log(p1.hasOwnProperty("myfunction"); // true;
console.log(p1.hasOwnProperty("name"); // true;

把構造函數的定義視爲本地屬性定義,把prototype屬性對象視爲引用屬性定義,這樣分開來理解,就會輕鬆多了。

對象的引用對象與構造函數的prototype屬性的關係

實例對象建立後,構造函數中的屬性和方法成爲本地屬性和方法,prototype中的屬性和方法成爲引用屬性和方法。我想知道一個實例對象產生後,怎麼知道這個對象是從哪裏引用的?對象不像構造函數,生下來就有prototype屬性,鏈接着引用對象。但對象有一個只讀的屬性__proto__,指向的就是它的引用對象(這叫隱式原型對象)。這個對象和構造函數的prototype指向的是同一個對象。由於引用對象是放在那裏供別人引用的,不會複製或從新產生,因此它是被直接定義到實例對象的__proto__的。

function Person(name) {
    this.name = name; // 屬性
}

var p = new Person();
console.log(p.__proto__ === Person.prototype); // true

prototype對象怎麼產生

這個prototype對象是怎麼來的呢?構造函數在JavaScript中產生時,隨即就有一個它的引用對象(prototype屬性指向的對象)產生了。它是伴隨着構造函數產生的。

延伸

  • prototype是構造函數生來就有的屬性,對象是沒有的。
  • 構造函數的prototype屬性的值是一個對象。即Person.prototype是一個對象
  • prototype的屬性和方法是共用屬性和方法,是全部實例對象都有,不一樣的屬性和方法在構造函數實現,按這樣建立對象就是類的繼承的方式了。產生的實例對象至關於都從父類繼承過來,這就是爲何把引用的這個對象叫原型(不叫引用或共享)的緣由了。

總結

  1. 構造函數中定義的屬性和方法是本地屬性和方法,prototype指向的對象定義的屬性和方法是引用屬性和方法。
  2. prototype定義的屬性和方法能被實例共同引用,是共同的部分,至關於每一個對象都有和引用對象一樣的屬性和方法,而自身的方法就經過構造函數來呈現。
相關文章
相關標籤/搜索