1. 程式人生 > >封裝——JavaScript的原型(prototype)

封裝——JavaScript的原型(prototype)

prototype,原型的初覽

function Person(){
Person.prototype.name="小花";
Person.prototype.age=18;
Person.prototype.say=function(){alert("姓名:"+this.name+",年齡:"+this.age);
}
}
var p1=new Person();
p1.say();
say();//報錯了

這樣我們發現window就無法訪問到say方法了,此時say方法只屬於person物件獨有的方法。很好的解決了封裝破壞的情況。

什麼是原型

上面我們爛了基於prototype建立物件的方式很好的解決了我們前面遇到的一系列問題,那麼到底什麼是原型,原型又是如何解決以上的問題的呢?我們下面來研究研究。

原型是js中非常特殊一個物件,當一個函式建立之後,會隨之產生一個原型物件,當通過這個函式的建構函式建立了一個具體的物件之後,在這個具體的物件中就會有一個屬性指向原型。這就是原型的概念。

鑑於原型的概念比較難以理解,我們就以上面的程式碼為例,畫圖為大家講解。

第一步:function Person(){}之後,記憶體中建立了一個Person物件,有一個prototype屬相,指向了Person物件的原型物件,而原型物件中存在了一個constructor的屬性,指向了Person物件。



第三種狀態:當根據Person建構函式建立一個物件後,該物件中存在一個_prop_的屬性,也指向了Person物件的原型物件,當我們呼叫物件的屬性或者方法時,首先在自己裡面找,找不到的話,就會去Person物件的原型物件中找


原型的基本知識到這裡也就差不多了,只有對上面的圖和程式碼能夠很好的理解,那麼原型的理解就沒問題,下面介紹幾種原型的檢測方式。

alert(Person.prototype.isprototypeof(p1))
//檢測p1的建構函式是否指向Person物件
alert(p1.constructor==Person)
//檢測某個屬性是不是自己記憶體中的
alert(p1.hasOwnProperty("name"));

可以使用delete語句來刪除我們賦予物件的自己屬性(注意:原型中的是無法刪除的)如:

delete p1.name;
p1.say();
alert(p1.hasOwnProperty("name"));

檢測在某個物件自己或者對應的原型中是否存在某個屬性。

alert("name" in p1);//true 
delete p2.name;//雖然刪除了自己的name屬性,但是原型中有
alert("name" in p2);//true 
//原型和自己中都有sex屬性
alert("sex" in Person)//false

那麼問題來了。如果檢測只在原型中,不在自己的屬性呢?

function hasPP(obj,prop){
if(!obj.hasOwnProperty(prop)){
if(prop in obj){
return true;
}
}
return false;
}
alert(hasPP(p1,"name"));
alert(hasPP(p2,"name"));

原型重寫

在上面的寫法中,我們已經解決了大量的問題,使用原型。但是如果我們的物件中存在大量的屬性或者方法的時候,使用上面的方式,感覺要寫大量的【物件.prototype.屬性名】這樣的程式碼,感覺不是很好,那麼我們可以使用JSON的方式來寫:

function Person(){}
Person.prototype={
name:"劉帥哥";
age:18;
say:function(){
alert("姓名:"+this.name+",年齡:"+this.age);
}
}
var p1=new Person();
p1.say();
var p2=new Person();
p2.age=20;
p2.name="小明";
p2.say();

但是這種寫法,我們是將該物件的原型覆蓋(注意:這兩種寫法不一樣的,第一種是擴充,第二種是覆蓋),就會出現如下的問題:

function Person(){}
Person.prototype={
name:"劉帥哥";
age:18;
say:function(){
alert("姓名:"+this.name+",年齡:"+this.age);
}
}
var p1=new Person();
p1.say();
var p2=new Person();
p2.age=20;
p2.name="小明";
p2.say();
//此時p1的構造器不再指向Person,而是指向了Object
//因為我們覆蓋了Person的原型,所以如果constructor比較重要的話,
//我們可以收到指向
alert(p1.constructor==Person)

此時就沒有問題了。但是原型重寫會給我們帶來非常有趣的現象。

function Person(){}
var p1=new Person();
Person.prototype.sayHello=function(){
alert("名字:"+this.name+",年齡:"+this.age);
}
Person.prototype={
constructor:Person,
name:"huahua",
age:18,
friends:["haha","xxx"],
say:function(){
alert("名字:"+this.name+",年齡:"+this.age);
}
}
var p2=new Person();
p2.say();//正確
p1.sayHello();//此時找不到name和age,但是程式碼正確
p1.say();//錯誤,因為原型重寫
p2.sayHello();//錯誤
這些程式碼要研究明白,必須配合之前原型的圖來看,下面我畫圖來說明問題:


因為原型重寫,需要大家根據原型的原理圖來理解,原型的知識也就這些了。

封裝——原型建立物件

因為原型存在,我們事先了物件的封裝,但是這種封裝也同樣可能存在問題的。

1.我們無法像使用建構函式的那樣將屬性傳遞用於設定值

2.當屬性中引用型別,可能存在變數值的重複

function Person(){};
Person.prototype={
constructor:Person,
name:"huahua",
age:18,
friends:["haha","xxx"],
say:function(){
alert(this.name+this.age+this.friends);
}
}
var p1=new Person();
p1.say();
var p2=new Person;
p2.name="xiaocao";
p2.say();
p1.friends.push("qq");
alert(p1.friends);//qq
alert(p2.friends);//qq  
//因為p1和p2物件都指向了同一個原型鏈,所以當p1屬性值發生變化時,p2也變化,p2的輸出也和p1一樣

終極方案——基於組合的物件定義

為了解決原型所帶來的問題,需要通過組合建構函式和原型來實現物件的建立將:屬相在建構函式中定義,將方法在原型中定義。這種有效集合了兩者的優點,是目前最為常用的一種方式。

//所以需要通過組合的封裝建構函式和原型來實現物件的建立
//基於組合的物件定義
//即屬性在構造方法中定義,方法在原型中定義
function Person(name,age,friends){
this.name=name;
this.age=age;
this.friends=friends;
}
//此時所有的屬性都儲存在自己的記憶體中
Person.prototype={
constructor:Person,
say:function(){
alert(this.name+this.age+this.friends);
}
}
//方法定義在原型中
var p1=new Person("花貓",19,["小花","小妹"]);
p1.friends.push("豆豆");
alert(p1.friends);
var p2=new Person("黑貓",19,["小草","小妹"]);
p2.friends.push("南瓜");
alert(p2.friends);


//基於動態原型的物件定義
function Person(name,age,friends){
this.name=name;
this.age=age;
this.friends=friends;
//判斷不存在的時候寫
//如果存在就不寫,減少記憶體消耗
if(!Person.prototype.say){
Person.prototype.say=function(){
alert(this.name+this.age+this.friends);
}
}
}
var p1=new Person("花貓",19,["小花","小妹"]);
p1.friends.push("豆豆");
alert(p1.friends);
var p2=new Person("黑貓",19,["小草","小妹"]);
p2.friends.push("南瓜");
alert(p2.friends);

JavaScript面向物件對應一個物件的方式,上述兩種都行,根據個人習慣而定。這也是JavaScript中面向物件的封裝。將屬性和方法封裝所對應的物件中,其他物件無法得到和訪問。