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


前言

荒廢了好幾天,在宿舍悶了幾天了,一直想着回家放松,什么也沒搞,論文就讓老師催吧。不過,閑的沒事干的感覺真是不好,還是看看書,寫寫博客吧,今天和大家說說函數的原型。

原型是什么

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

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實例出來試試

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屬性,這個屬性是一個指針,指向一個原型對象,這個原型對象中的屬性和方法是這個函數的實例所共有的。現在我們看看這個原型對象中的屬性,看這幅圖

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

嗯,這樣就對了,無論啥時候,只要創建一個新函數,就為給這個函數創建一個prototype屬性,在默認的情況下,所有的原型對象會自動添加一個constructor屬性,從名字就可以看出來應該指向構造函數,看圖

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

屬性中還有一個__prototype__屬性,我們先放一下,我們還是看new出來那個實例的圖,這次我把屬性展開大家看看

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高級程序設計》,那上面寫得比較好。小伙伴們都要回家了吧,提前祝大家春節快樂,馬上有錢,立馬變土豪。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM