面向對象是javascript核心內容之一,今天就來討論對象和原型.
首先討論創建對象幾種常見的方式:
(1) 最經典,最簡單的方法.利用object
var o=new Object();//創建對象;
o.name='jack';//添加屬性
o.sayName=function(){alert(this.name);}//添加方法;
點評:這種方法明顯不適合創建多個對象。
(2)對象字面量形式:注意語法格式
var o={
name:'jack',
sayName:function(){alert(this.name);}
}
(3)一種設計模式 ‘工廠模式’
function createObject(name){
var o=new Object();
o.name=name;
o.sayName=function(){alert(this.name);}
return o;
}
var obj=createObject('jack'); //創建對象
alert(obj instanceof Object) //ture
alert(obj instanceof createObject);//false;
alert(type obj )//Object
點評: 這種方式不難理解; 就是利用函數初始化一個Object對象,可以創建多個對象;
缺點:無法判斷對象的具體類型, 都是object對象。
(4) 構造方法
function Person(name){
this.name=name;
this.sayName=function(){alert(this.name);}
}
var o=new Person('jack');//創建對象;
alert(o.name);//訪問屬性
o.sayName();//訪問方法;
alert(o instanceof Person);//true
alert(o instanceof Object);//true
alert(type o); //object
那么 這種構造方法是如何創建對象的?
var o=new Person('jack');//創建對象;
這時調用構造方法,同時自動創建(底層,看不見) var o=new Object();然后 this=o; 我們知道此時this就代表對象了,創建該對象的屬性和方法(執行代碼); 返回this對象(看不見);
我們的o接受this的引用,指向了Object對象,同時也就可以訪問屬性和方法了!
4 構造方法結合原型------"默認"模式.
我們知道通過構造函數創建對象是有一弊端的, 就是每一個創建的對象都有各自一份屬性和方法; 這里的弊端就是方法的重復。另外我們一旦寫好了構造函數,不能在外面為構造函數添加屬性和方法。為了解決這個問題 javascript為我們提供了prototype的一個屬性。值得注意的是這個構造函數(Function 對象)的屬性,不是對象實例的屬性(注意這句話)。
具體是這樣的:
function Person(name){
this.name=name;}
Person.prototype.sayName=function(){alert(this.name);}
同時使用構造函數和原型的好處是:可以節省內存。這種模式基本上就創建對象的默認模式。
5 其他方法;
現在問題來了:原型是怎么回事?
先看一張圖---------來自《javascript高級程序設計》 這是很有意思的圖
解釋:
在這里Person是一個構造函數, 我們看到有一個prototype屬性,這個就是原型屬性。他其實是一個指針, 指向一個對象, 這個對象叫原型對象 在這里是 Person.prototype
Person創建person1 person2 二個實例 ,圖上我們可以看到實例中的 [[prototype]]屬性,這是內部屬性 (基本上不能外部訪問) 這個對象是指向原型對象的(關鍵)。我們還可以看到
原型對象的constructor屬性是指向Person的。圖中我們可以看到以上這些。 這樣 Person.prototype.sayName=function(){alert(this.name);} 就是向原型對象中添加方法,我們通過person1.sayName();
為什么可以訪問方法?這就與[[prototype]]有關了,首先person1先找實例屬性有沒有叫sayName的方法,結果沒有,那么就通過 [[prototype]] 找到原型對象,查找有沒有sayName的方法,結果有那么就拿到了。
因為一個構造函數只有一個原型對象與之對應,所有實例的[[prototype]]都是指向同一個原型對象的。所以原型里面的屬性是公有屬性,方法也是共有的。這個[[prototype]]在chrome ff中是 __proto__ 我們這樣
alert(person1.__proto__); 結果是[Object] ; 我們是不能通過實例直接為原型添加方法和屬性的,只有通過構造函數才可以。值得注意的是Object.getPrototypeOf('object');在這里 Object.getPrototypeOf(person1)
返回值是原型對象,這也是原型鏈繼承的關鍵之處(這里先不討論)。原型就是這樣的。
一個問題:
說到原型,我們為一個構造函數添加方法時可以這樣!
Person.prototype={
school:'一中',
saySchoo:function(){
alert(this.school);
}
//code
};
這是對象字面量形式,我們會看到一些程序員這樣寫。那么有問題嗎?
有的,二個問題?
(1)constructor屬性問題。看上面我們知道這樣寫本質上重寫了原型對象.constructor沒有說明指向誰,那么就是window,不再是Person。我們可以這樣
Person.prototype={
school:'一中',
constructor:Person;
saySchoo:function(){
alert(this.school); }
//code};
我們一般這樣就可以了,不過和原來原型有一點個區別,就是constructor為可枚舉(for in打印出該屬性)。原來是不可以枚舉的。 要和原來原型對象一樣,那么應該這樣。
Person.prototype={
school:'一中',
saySchoo:function(){
alert(this.school);
}
//code
};
Object.defineProperty(Person.prototype,"constructor",{
value:Person,
enumerable:false; //不可以枚舉;
});
這樣就接近了。我們也可以不去設置,假定沒有什么影響。
(2)第二個問題就是
function Person(){}
var o=new Person();
Person.prototype={
school:'一中',
saySchoo:function(){
alert(this.school);
}//code
};
o.saySchool(); //無法訪問,undefined
原因是: o的[[prototype]]指向原來的 原型對象;現在重新寫了,原型對象的位置變了,(函數中prototype不再指向舊的原型對象),但是o實例環視原來的,原來就沒有這個方法,所有訪問不到。
ps:可能這些內容過於詳細,不過了解還是好的,我們很多時候還是會使用這種模式重寫原型對象的。就可能會有上面的二個bug;