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
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
構造函數中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屬性,鏈接着引用對象。但對象有一個只讀的屬性__proto__,指向的就是它的引用對象(這叫隱式原型對象)。這個對象和構造函數的prototype指向的是同一個對象。由於引用對象是放在那裏供別人引用的,不會複製或從新產生,因此它是被直接定義到實例對象的__proto__的。
function Person(name) { this.name = name; // 屬性 } var p = new Person(); console.log(p.__proto__ === Person.prototype); // true
這個prototype對象是怎麼來的呢?構造函數在JavaScript中產生時,隨即就有一個它的引用對象(prototype屬性指向的對象)產生了。它是伴隨着構造函數產生的。