【javascript基礎】四、原型與原型鏈

前言

荒廢了好幾天,在宿舍悶了幾天了,一直想着回家放鬆,什麼也沒搞,論文就讓老師催吧。不過,閒的沒事幹的感受真是很差,仍是看看書,寫寫博客吧,今天和你們說說函數的原型。javascript

原型是什麼

第一次看到這個的時候,沒太理解這個概念,其實也就是一個概念唄,沒啥神祕的。書上說每一個函數都有一個prototype屬性(原型屬性),這個屬性是一個指針,指向一個對象(原型對象),這個對象包含這個函數建立的實例的共享屬性和方法。也就是說原型對象中的屬性和方法是全部實例共享的,打住,那咱們就先建立一個函數看看,原型是什麼東東java

var test = function(){}
console.log(test.prototype);//Object {}

看來,是這的有這麼一個屬性,能夠看出是一個對象,可是默認的是一個空的對象,既然是一個對象,那咱們就能夠給它添加屬性和方法嘍,試試看數組

var test = function (){}
test.prototype.name = "hainan";
test.prototype.age = "25";
test.prototype.getName = function(){console.log(this.name);}
console.log(test.prototype);//Object {}
View Code

咱們成功的添加了屬性和方法,驕傲吧,對象中的屬性一會咱們在解釋,如今看看咱們修改以後的原型對象,有啥用呢?剛纔咱們說過,原型對象中的屬性和方法是這個函數new出來實例所共享的,那咱們就new實例出來試試ide

var test = function test(){}
test.prototype.name = "hainan";
test.prototype.age = "25";
test.prototype.getName = function(){console.log(this.name);}
console.log(test.prototype);//Object {}
var o1 = new test();
console.log(o1.getName());
console.log(o1.name);
var o2 = new test();
console.log(o2.getName());
console.log(o2.name);
View Code

看上面的圖,咱們new出來兩個實例,這兩個實例中並無name對象和getName()方法,可是咱們卻使用了該屬性和方法,就是由於函數的原型對象中存在這個屬性和方法,而且是每一個實例均可以使用的,這就是原型的神祕之處。也就是說之後咱們想在每個實例中添加屬性和方法,那咱們就把這個共有的屬性或方法直接添加到原型對象上就能夠了。函數

理解原型對象

原型對象

如今咱們知道了,函數有一個prototype屬性,這個屬性是一個指針,指向一個原型對象,這個原型對象中的屬性和方法是這個函數的實例所共有的。如今咱們看看這個原型對象中的屬性,看這幅圖this

咱們給一個原型屬性添加屬性以後,這個對象就不是空得了,看第一個圖,上面打印出來的Object是空的,其實這個對象不是空的,只是有些屬性沒有被枚舉出來,看下圖spa

嗯,這樣就對了,不管啥時候,只要建立一個新函數,就爲給這個函數建立一個prototype屬性,在默認的狀況下,全部的原型對象會自動添加一個constructor屬性,從名字就能夠看出來應該指向構造函數,看圖prototype

指向了構造函數,也就是test.prototype.constructor === test。設計

屬性中還有一個__prototype__屬性,咱們先放一下,咱們仍是看new出來那個實例的圖,此次我把屬性展開你們看看3d

var test = function test(){}
test.prototype.name = "hainan";
test.prototype.age = "25";
test.prototype.getName = function(){console.log(this.name);}
console.log(test.prototype);//Object {}
var o1 = new test();
console.log(o1);
View Code

咱們分別打印了test的原型對象和一個實例對象,咱們能夠看到原型對象和實例對象中都有一個__proto__對象,可是指向不一樣,原型對象中的__proto__指向Object,而實例中的__proto__指向內部明叫test的對象,展開這個對象能夠看到就是原型對象。就是說每個實例中有一個__proto__屬性指向原型對象。是否是有點暈呢,畫個圖看看先

 

是否是清楚點了呢,每個實例的內部都包含一個內部屬性__proto__,指向了構造函數的原型,就是這個屬性鏈接了實例和原型對象之間的關係,而且咱們知道實例中不包含name屬性和getName方法,可是咱們卻使用了getName(),就是經過這個__proto__屬性查找的。

你們應該發現了test的原型對象中也有一個__proto__屬性,這個屬性指向誰呢,咱們來分析一下。咱們知道了__proto__屬性存在一個實例中並指向的是一個原型對象,如今就是說test的原型對象時某一個對象的實例嘍,由於它有一個__proto__屬性嘛。那test的原型對象是哪一個對象的實例呢?咱們知道javascript全部的對象都是基於Object這個對象的,都是Object的實例,咱們大膽的猜想就是Oject的實例,咱們展開這個屬性看看就知道咱們猜的對不對了,看圖

 

 嗯,咱們看到了__proto__屬性指向的對象中存在一些方法,這些方法就是咱們前面介紹的Object對象的方法,好牛,咱們猜對了。咱們剛纔說了一下,實例能夠共享原型的方法和屬性,也就是test的原型可使用Object原型中方法,而test的實例可使用test的原型中的方法,也就是說test的實例可使用Object原型中的方法,嗯,就是這樣,咱們試一下,看代碼,使用一個簡單的函數試一下就知道了

var test = function test(){}
test.prototype.name = "hainan";
test.prototype.age = "25";
test.prototype.getName = function(){console.log(this.name);}
var o1 = new test();
console.log(o1.toString());
View Code

咱們使用了Object原型中的方法,咱們再一次猜對了。如今咱們把上面的圖補充完整

如今就完整了,這就是這個例子的完整原型圖形。咱們之前說過,全部的應用類型都是繼承Object,全部函數的默認原型都是Object的實例,所以默認原型都包含一個默認指針指向Object.prototype。這就是咱們所說的原型鏈,繼承就是經過原型鏈實現的,這就是全部的自定義類型都會繼承toString()和valueOf()等默認方法的根本緣由。Object是全部引用類型的父類,能夠這麼理解。

isPrototypeOf

使用方法a.isprototypeOf(b),判斷對象a是不是實例b__proto__指向的原型對象,若是是返回true,不然返回false。看個例子

var test = function test(){}
test.prototype.name = "hainan";
test.prototype.age = "25";
test.prototype.getName = function(){console.log(this.name);}
var o1 = new test();
console.log(test.prototype.isPrototypeOf(o1));//true

hasOwnProperty

這個方法是檢測一個屬性是否存在實例中,存在原型中會返回false,看例子

var test = function test(){}
test.prototype.name = "hainan";
test.prototype.age = "25";
test.prototype.getName = function(){console.log(this.name);}
var o = new test();
console.log(o.hasOwnProperty("name"));//false
o.name = "xing";//給實例添加屬性
console.log(o.hasOwnProperty("name"));//true

屬性查找

對象實例能夠訪問保存在原型中的值,可是不能重寫原型中的值。每次要讀取某一個屬性時,都會執行一次搜索:首先在對象自己開始查找,若是查找到了就返回這個屬性,若是沒有找到,則繼續搜索__proto__指向的原型對象,若是尚未找到,則繼續搜索原型對象中__proto__指向的原型對象,這樣一直迭代下去,這就是原型鏈的做用。看例子

var test = function test(){}
test.prototype.name = "hainan";
test.prototype.age = "25";
test.prototype.getName = function(){console.log(this.name);}
var o = new test();
console.log(o.name);//hainan
o.name = "xing";//給實例添加屬性
console.log(o.name);//xing
delete o.name;//刪除實例的name屬性
console.log(o.name);//hainan

重寫原型對象

咱們已經知道怎麼給一個原型對象添加屬性和方法,可是你們都會想到一個簡單地方法來一塊兒添加,就像這樣

var test = function test(){}
test.prototype = {
  name : "hainan",
  age : "25",
  getName : function(){return this.name}    
};
var o = new test(); o.getName();//"hainan"
console.log(o.constructor == test);//false

咱們思考一下,上面的代碼test.prorotype如今已經指向了一個新的對象,已經不是原來那個默認的原型對象了,原來的默認原型對象咱們以前只是添加屬性並無重寫它,全部他的內部屬性仍是存在了,如今咱們重寫了這個對象,即prototype指向了一個新的對象了,那他原來的屬性constructor就沒有了,若是咱們之後會使用這個屬性,那咱們應該人爲的設置,例如

var test = function test(){}
test.prototype = {
  constructor : test,
  name : "hainan",
  age : "25",
  getName : function(){return this.name}    
};
var o = new test(); o.getName();//"hainan"
console.log(o.constructor == test);//true

原型動態性

看個例子先

var test = function test(){}
var o = new test();
test.prototype.name = "hainan";
console.log(o.name);

咱們通常的時候確定是先設置原型在建立實例,這在任何狀況下都是沒有問題的。咱們建立實例是在給原型添加屬性以後,即便這樣咱們也能夠在實例中使用這個屬性,這就是原型的動態性。這是因爲在原型中查找值的過程是一次搜索,全部你修改的屬性能夠當即在實例中獲得體現。可是重寫一個函數的原型就不是這樣了,看例子

var test = function test(){}
var o = new test(); 
o.getName();//TypeError: Object #<test> has no method 'getName' test.prototype = { constructor : test, name : "hainan", age : "25", getName : function(){return this.name} };

出現了錯誤,也就是我先建立了一個實例,在重寫函數的原型對象這是不行的。緣由是這樣的,因爲實例中的__proto__指針只是指向原型,而不是構造函數,上面的這段代碼中,咱們建立實例的時候,這個實例中的__proto__指向的是函數的默認原型對象,當咱們重寫了這個函數的原型對象時,雖然函數的prototype屬性指向了新的對象,可是實例中的已經建立好了,它__proto__並無改變,這個屬性仍是指向的是默認的原型對象,全部它的內部沒有這個方法。可是若是在重寫原型以後建立一個實例的話,這個新的實例的__proto__指向的就是新的原型對象了,像這樣

var test = function test(){}
test.prototype = {
  constructor : test,
  name : "hainan",
  age : "25",
  getName : function(){return this.name}    
};
var o = new test(); 
o.getName();//"hainan"

原生對象的原型

原生對象和咱們自定義對象同樣,都存在原型,原生對象的方法都存在原型中,這樣咱們建立一個新的對象實例時,這些對象就會擁有這些方法,固然咱們可擴展原型對象,這樣咱們之後new出來的原型對象的實例就會共享這個方法或屬性了。

console.log(String.prototype.slice);//function

咱們擴展一個Array類型,增長一個函數contains(),判斷數組中是否包含一個值

Array.prototype.contains = function(){
   var length = arguments.length;
   if(length != 1) return;
   for(var i=0;l=this.length,i<l;++i ){
      if(arguments[0] === this[i]) return true;
   }
   return false;
}
var arr = [1,2,3];
console.log(arr.contains(1));//true
console.log(arr.contains(4));//false

這就成了。

PS:給你們截一個圖你們看看,不懂的能夠在下面討論下

 

小結

就先寫到這吧,你們有不懂的能夠在下面討論吧,我不懂的話我再去問大神,大夥要是以爲寫得亂的話推薦去看看《javascript高級程序設計》,那上面寫得比較好。小夥伴們都要回家了吧,提早祝你們春節快樂,立刻有錢,立馬變土豪。

相關文章
相關標籤/搜索