學習了《javascript高級程序設計》,感覺是本好書,很經典。本文是對其中面向對象設計的一個簡單的總結。有很多不到位的地方還請指出。
面向對象編程的出現使得程序設計與軟件開發進入到一個新的時代。面前支持面向對象程序設計的語言有很多,比如java、python、C++等。Javascript在web前端設計發揮重要作用。在某種程度上javascript也是面向對象的程序設計語言。
所謂面向對象就是將世間萬物都看成一個對象,每個對象有自己的屬性和方法。比如狗可以看做一個對象,它有自己的屬性:名稱、種類、毛色等,它也可以有自己的方法:叫、跑等。對於面向對象的程序設計,我們不用管某個類的某個方法是如何實現的,我們只需要關注它給我們提供什麼方法,通過向類傳遞信息的方式可以調用這些方法,瞭解這些屬性。
1、創建對象
面向對象的程序設計必須將方法與屬性封裝在一個對象內。所以在進行程序設計的時候首先要創建一個對象。Javascript與java不同,它創建對象的方法有很多種。
1) 創建object實例
var dog = newObject();
dog.name = “wangcai”;
dog.color = “yellow”;
dog.run =function(){
alert(“I’m Running”);
}
如上例子所示,首先創建一個Object實例,然後爲這個實例添加方法及屬性。但此方法並不好,每創建一條狗,都需要按照上面的方法,會產生很多重複的代碼。於是有了下面的工場模型。
2) 工廠模式
FunctioncreateDog(name, color){
var dog = new Object();
dog.name = name;
dog.color = color;
dog.run = function(){
alert(“I’m running”);
}
}
var dog1 = createDog(“wangcai1”,”yellow”);
var dog2 =createDog(“wangcai2”,”grey”);
這樣每當想要創建一條狗的時候,只要調用createDog這個方法就可以了。這樣可以節省很多代碼。工廠模式雖然解決了代碼冗餘問題,但還有一個重要的問題是無法實現對象識別的問題,我們無法識別我們所創建的對象是一條狗還是一個人。這個時候又出現了一個新的解決方法來解決對象識別的問題。
3) 構造函數
functionDog(name, color){
this.name = name;
this.color = color;
this.run = function(){
alert(“master, I’m Running”);
}
}
var dog1 = newDog(“wangcai”,”yellow”);
var dog2 = newDog(“wangcai”, “blue”);
使用構造函數方式,在構造函數內部不用創建一個Object對象,也沒有return 語句。
在使用構造函數創建對象的時候要使用new 關鍵詞,後面跟上方法名以及參數。
此時就可以判斷dog1和dog2的類型。可以使用instanceof關鍵詞
alert(dog1instanceof Dog)//true
alert(dog1instanceof Object)//true;
使用構造函數的方法通過關鍵詞instanceof就可以判斷,對象實例的類型。
使用構造函數可以判斷實例的類型,但每創建一個對象,都要創建一個構造函數內部方法,這樣比較佔內存。所以可以通過以下方式來創建對象。
functionDog(name, color){
this.name = name;
this.color = color;
this.sayHi = SayHi;
}
function SayHi(){
alert(“hi, I’m” + this.name);
}
通過上面這種構造全局變量的方式可以解決每創建一個對象都要創建一個方法的問題,因爲所有對象的方法都指向這個全局函數。不過這種方法也存在問題,如果一個對象有很多個方法,那就要創建多個全局函數,但這與全局函數的真正含義不符。還有更重要的一點,在外部創建很多個全局函數不符合面向對象的編程思想。面向對象的編程思想需要將所有的對象以及屬性都封裝在一個類的內部。於是乎,爲了解決這個問題引出了javascript面向對象編程的一個重要概念:原型。
原型
個人認爲原型屬性與java中的靜態函數以及靜態方法相類似,但又有不同之處。每創建一個函數,則javascript會根據一定規則爲函數創建一個屬性prototype,這個屬性包含一個指向原函數的指針。
<script type="text/javascript">
function Dog(name){
this.name = name;
this.sayHi = function(){
alert("Hi");
}
}
Dog.prototype.sayHello = function(){
alert("hello " +this.name);
}
Dog.prototype.age = 18;
var dog1 = new Dog("wangcai")
Dog.name = “wangcai2”;
dog1.sayHello();
dog1.sayHi();
</script>
如上面例子所示,利用構造函數的方式創建一個類,如果爲其添加原型方法,可以使用
functionName + dot+ propertyName = propertyValue的方法。所有的原型方法爲所有的實例共享,實例可以訪問原型方法和屬性,這是因爲實例中有一個_proto_的屬性指向原型。
更爲簡單的原型語法
可以使用對象字面量來重寫原型語法
例子:
function Dog(){
}
Dog.prototype ={
Name = “wangcai”;
age = 18;
color = “green”;
}
原型語法很好用,但原型語法並不是完美無缺的,原型語法的問題在於它的值的共享性,任何實例都共享原型中的值和方法。對於包含基本類型的屬性並不是問題,可以再實例中重寫方法來完成。但對於一些包含引用類型的屬性來說,問題較爲突出。如下面例子所示:
<script type= "text/javascript">
function Person(){
}
Person.prototype = {
constructor :Person,
name : "guxin",
age : 18,
job : "computerscience",
friends :["zhouqi","wangjianfei"]
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("xiaoqiaofen");
alert(person2.friends);//zhouqi,wangjianfei, xiaoqiaofeng
</script>
從上面的例子可以看出,原本是打算爲person1添加一個朋友,但由於prototype裏的屬性是共享的,所以在爲person1添加屬性的同時也爲person2添加了屬性,這樣就會產生問題。爲了解決這樣一個問題,可以採用以下這種組合使用構造函數模式和原型模式。
<scripttype= "text/javascript">
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = [];
}
Person.prototype ={
constructor : Person,
SayName : function(){
alert(this.name);
}
}
var person1 = newPerson("guxin", 29, "teacher");
var person2 = newPerson("zhangmin", 30, "teacher");
person1.friends.push("anna");
alert(person1.friends);
alert(person2.friends);
</script>
Prototype內的屬性和函數是共享的,但構造函數內的屬性和方法並不是共享的。從上面的例子可以看出,採用這種方式可以避免由於prototype共享而產生的問題。