JS中的原型和原型鏈
講原型的時候,咱們應該先要記住如下幾個要點,這幾個要點是理解原型的關鍵:程序員
一、全部的引用類型(數組、函數、對象)能夠自由擴展屬性(除null之外)。數組
二、全部的引用類型都有一個’_ _ proto_ _'屬性(也叫隱式原型,它是一個普通的對象)。瀏覽器
三、全部的函數都有一個’prototype’屬性(這也叫顯式原型,它也是一個普通的對象)。函數
四、全部引用類型,它的’_ _ proto_ _'屬性指向它的構造函數的’prototype’屬性。this
五、當試圖獲得一個對象的屬性時,若是這個對象自己不存在這個屬性,那麼就會去它的’_ _ proto_ _'屬性(也就是它的構造函數的’prototype’屬性)中去尋找。
spa
咱們先來看一個原型的例子。prototype
// 實例對象和構造函數之間的關係: //1. 實例對象是經過構造函數來建立的---建立的過程叫實例化 //2.如何判斷對象是否是這個數據類型? //1) 經過構造器的方式 實例對象.構造器==構造函數名字 //2) 對象 instanceof 構造函數名字 //實例對象中有__proto__這個屬性,叫原型,也是一個對象,這個屬性是給瀏覽器使用,不是標準的屬性----->__proto__----->能夠叫原型對象 //構造函數中有prototype這個屬性,叫原型,也是一個對象,這個屬性是給程序員使用,是標準的屬性------>prototype--->能夠叫原型對象
function Foo(name, age) { this.name = name; this.age = age; } /*根據要點3,全部的函數都有一個prototype屬性,這個屬性是一個對象 再根據要點1,全部的對象能夠自由擴展屬性 因而就有了如下寫法*/ Foo.prototype = { // prototype對象裏面又有其餘的屬性 showName: function () { console.log("I'm " + this.name);//this是什麼要看執行的時候誰調用了這個函數 }, showAge: function () { console.log("And I'm " + this.age);//this是什麼要看執行的時候誰調用了這個函數 } } var fn = new Foo('小明', 19) /*當試圖獲得一個對象的屬性時,若是這個對象自己不存在這個屬性,那麼就會去它 構造函數的'prototype'屬性中去找*/ fn.showName(); //I'm 小明 fn.showAge(); //And I'm 19
構造函數能夠實例化對象
構造函數中有一個屬性叫prototype,是構造函數的原型對象
構造函數的原型對象(prototype)中有一個constructor構造器,這個構造器指向的就是本身所在的原型對象所在的構造函數
實例對象的原型對象(__proto__)指向的是該構造函數的原型對象
構造函數的原型對象(prototype)中的方法是能夠被實例對象直接訪問的
這就是原型,很好理解。那爲何要使用原型呢?code
function Person(name, age) { this.name = name; this.age = age; } //經過原型來添加方法和屬性,解決數據共享,節省內存空間 Person.prototype.occupation = '學生'; Person.prototype.eat = function () { console.log(this.name + "吃涼菜"); }; var p1 = new Person("小明", 20); var p2 = new Person("小紅", 30); console.log(p1.eat == p2.eat);//true p1.eat();//小明吃涼菜 p2.eat();//小紅吃涼菜 console.log(p1.name + '是個' + p1.occupation);//小明是個學生 console.log(p2.name + '是個' + p2.occupation);//小紅是個學生
#####下面這段話能夠幫助理解原型鏈
根據要點5,當試圖獲得一個對象的屬性時,若是這個對象自己不存在這個屬性,那麼就會去它構造函數的’prototype’屬性中去尋找。那又由於’prototype’屬性是一個對象,因此它也有一個’_ _ proto_ _'屬性。對象
那麼咱們來看一個例子:blog
function Foo(name, age) { this.name = name; this.age = age; } Object.prototype.toString = function () { //this是什麼要看執行的時候誰調用了這個函數。 console.log("I'm " + this.name + " And I'm " + this.age); } var fn = new Foo('小明', 19); fn.toString(); //I'm 小明 And I'm 19 console.log(fn.toString === Foo.prototype.__proto__.toString); //true console.log(fn.__proto__ === Foo.prototype)//true console.log(Foo.prototype.__proto__ === Object.prototype)//true console.log(Object.prototype.__proto__ === null)//true
首先,fn的構造函數是Foo()。因此:
fn._ _ proto _ _=== Foo.prototype
又由於Foo.prototype是一個普通的對象,它的構造函數是Object,因此:
Foo.prototype._ _ proto _ _=== Object.prototype
經過上面的代碼,咱們知道這個toString()方法是在Object.prototype裏面的,當調用這個對象的自己並不存在的方法時,它會一層一層地往上去找,一直到null爲止。
因此當fn調用toString()時,JS發現fn中沒有這個方法,因而它就去Foo.prototype中去找,發現仍是沒有這個方法,而後就去Object.prototype中去找,找到了,就調用Object.prototype中的toString()方法。
這就是原型鏈,fn可以調用Object.prototype中的方法正是由於存在原型鏈的機制。
另外,在使用原型的時候,通常推薦將須要擴展的方法寫在構造函數的prototype屬性中,避免寫在_ _ proto _ _屬性裏面。