什麽是面向對象?
面向對象編程主要通過代碼代表現實世界中的實質對象。要創建對象,首先需要寫壹個“類”來定義。 類幾乎可以代表所有的東西:賬戶,員工,導航菜單,汽車,植物,廣告,飲料,等等。而每次要創建對象的時候,就從類實例化壹個對象。換句話說,就是創建類的實例做為對象。事實上,通常處理壹個以上的同類事物時就會使用到對象。另外,只需要簡單的函數式程序就可以做的很好。對象實質上是數據的容器。因此在壹個employee對象中,妳可能要儲存員工號,姓名,入職日期,職稱,工資,資歷,等等。
對象也包括處理數據的函數(也叫做“方法”)。方法被用作媒介來確保數據的完整性,以及在儲存之前對數據進行轉換。例如,方法可以接收任意格式的日期然後在儲存之前將其轉化成標準化格式。最後,類還可以繼承其他的類。繼承可以讓妳在不同類中重復使用相同代碼。例如,銀行賬戶和音像店賬戶都可以繼承壹個基本的賬戶類,裏面包括個人信息,開戶日期,分部信息,等等。然後每個都可以定義自己的交易或者借款處理等數據結構和方法。
警告:JavaScript面向對象是不壹樣的
在上壹節中,概述了經典的面向對象編程的基本知識。說經典是因為JavaScript並不遵循這些規則。相反地,JavaScript的類是寫成函數的樣子,而繼承則是通過原型實現的。原型繼承基本上意味著使用原型屬性來實現對象的繼承,而不是從類繼承類。
對象的實例化
以下是JavaScript中對象實例化的例子:
// 定義Employee類
function Employee(num, fname, lname) {
this.getFullName = function () {
return fname + " " + lname;
}
};
// 實例化Employee對象
var john = new Employee("4815162342", "John", "Doe");
alert("The employee's full name is " + john.getFullName());在這裏,有三個重點需要註意:
1 “class”函數名的第壹個字母要大寫。這表明該函數的目的是被實例化而不是像壹般函數壹樣被調用。
2 在實例化的時候使用了new操作符。如果省略掉new而僅僅調用函數則會產生很多問題。
3 因為getFullName指定給this操作符了,所以是公***可用的,但是fname和lname則不是。由Employee函數產生的閉包給了getFullName到fname和lname的入口,但同時對於其他類仍然是私有的。
原型繼承
下面是JavaScript中原型繼承的例子:
// 定義Human類
function Human() {
this.setName = function (fname, lname) {
this.fname = fname;
this.lname = lname;
}
this.getFullName = function () {
return this.fname + " " + this.lname;
}
}
// 定義Employee類
function Employee(num) {
this.getNum = function () {
return num;
}
};
//讓Employee繼承Human類
Employee.prototype = new Human();
// 實例化Employee對象
var john = new Employee("4815162342");
john.setName("John", "Doe");
alert(john.getFullName() + "'s employee number is " + john.getNum());這壹次,創建的Human類包含人類的壹切***有屬性——我也將fname和lname放進去了,因為不僅僅是員工才有名字,所有人都有名字。然後將Human對象賦值給它的prototype屬性。
通過繼承實現代碼重用
在前面的例子中,原來的Employee類被分解成兩個部分。所有的人類通用屬性被移到了Human類中,然後讓Employee繼承Human。這樣的話,Human裏面的屬性就可以被其他的對象使用,例如Student(學生),Client(顧客),Citizen(公民),Visitor(遊客),等等。現在妳可能註意到了,這是分割和重用代碼很好的方式。處理Human對象時,只需要繼承Human來使用已存在的屬性,而不需要對每種不同的對象都重新壹壹創建。除此以外,如果要添加壹個“中間名字”的屬性,只需要加壹次,那些繼承了 Human 類的就可以立馬使用了。反而言之,如果我們只是想要給壹個對象加“中間名字”的屬性,我們就直接加在那個對象裏面,而不需要在Human 類裏面加。
1. Public(公有的)和Private(私有的)
接下來的主題,我想談談類中的公有和私有變量。根據對象中處理數據的方式不同,數據會被處理為私有的或者公有的。私有屬性並不壹定意味著其他人無法訪問。可能只是某個方法需要用到。
只讀
有時,妳只是想要在創建對象的時候能有壹個值。壹旦創建,就不想要其他人再改變這個值。為了做到這點,可以創建壹個私有變量,在實例化的時候給它賦值。
function Animal(type) {
var data = [];
data['type'] = type;
this.getType = function () {
return data['type'];
}
}
var fluffy = new Animal('dog');
fluffy.getType();
// 返回 'dog'在這個例子中,Animal類中創建了壹個本地數組data。當 Animal對象被實例化時,傳遞了壹個type的值並將該值放置在data數組中。因為它是私有的,所以該值無法被覆蓋(Animal函數定義了它的範圍)。壹旦對象被實例化了,讀取type值的唯壹方式是調用getType方法。因為getType是在Animal中定義的,因此憑借Animal產生的閉包,getType可以進到data中。這樣的話,雖可以讀到對象的類型卻無法改變。
有壹點非常重要,就是當對象被繼承時,“只讀”技術就無法運用。在執行繼承後,每個實例化的對象都會***享那些只讀變量並覆蓋其值。最簡單的解決辦法是將類中的只讀變量轉換成公***變量。但是妳必須保持它們是私有的,妳可以使用Philippe在評論中提到的技術。
Public(公有)
當然也有些時候妳想要任意讀寫某個屬性的值。要實現這壹點,需要使用this操作符。
function Animal() {
this.mood = '';
}
var fluffy = new Animal();
fluffy.mood = 'happy';
fluffy.mood;
// 返回 'happy'
這次Animal類公開了壹個叫mood的屬性,可以被隨意讀寫。同樣地,妳還可以將函數指定給公有的屬性,例如之前例子中的getType函數。只是要註意不要給getType賦值,不然的話妳會毀了它的。
完全私有
最後,可能妳發現妳需要壹個完全私有化的本地變量。這樣的話,妳可以使用與第壹個例子中壹樣的模式而不需要創建公有方法。
function Animal() {
var secret = "You'll never know!"
}
var fluffy = new Animal();
2. 寫靈活的API
既然我們已經談到類的創建,為了保持與產品需求變化同步,我們需要保持代碼不過時。如果妳已經做過某些項目或者是長期維護過某個產品,那麽妳就應該知道需求是變化的。這是壹個不爭的事實。如果妳不是這麽想的話,那麽妳的代碼在還沒有寫之前就將註定荒廢。可能妳突然就需要將選項卡中的內容弄成動畫形式,或是需要通過Ajax調用來獲取數據。盡管準確預測未來是不大可能,但是卻完全可以將代碼寫靈活以備將來不時之需。
Saner參數列表
在設計參數列表的時候可以讓代碼有前瞻性。參數列表是讓別人實現妳代碼的主要接觸點,如果沒有設計好的話,是會很有問題的。妳應該避免下面這樣的參數列表:
function Person(employeeId, fname, lname, tel, fax, email, email2, dob) {
};
這個類十分脆弱。如果在妳發布代碼後想要添加壹個中間名參數,因為順序問題,妳不得不在列表的最後往上加。這讓工作變得尷尬。如果妳沒有為每個參數賦值的話,將會十分困難。例如:
var ara = new Person(1234, "Ara", "Pehlivanian", "514-555-1234", null, null, null, "1976-05-17");操作參數列表更整潔也更靈活的方式是使用這個模式:
function Person(employeeId, data) {
};有第壹個參數因為這是必需的。剩下的就混在對象的裏面,這樣才可以靈活運用。
var ara = new Person(1234, {
fname: "Ara",
lname: "Pehlivanian",
tel: "514-555-1234",
dob: "1976-05-17"
});這個模式的漂亮之處在於它即方便閱讀又高度靈活。註意到fax, email和email2完全被忽略了。不僅如此,對象是沒有特定順序的,因此哪裏方便就在哪裏添加壹個中間名參數是非常容易的:
var ara = new Person(1234, {
fname: "Ara",
mname: "Chris",
lname: "Pehlivanian",
tel: "514-555-1234",
dob: "1976-05-17"
});類裏面的代碼不重要,因為裏面的值可以通過索引來訪問:
function Person(employeeId, data) {
this.fname = data['fname'];
};如果data['fname'] 返回壹個值,那麽他就被設定好了。否則的話,沒被設定好,也沒有什麽損失。
讓代碼可嵌入
隨著時間流逝,產品需求可能對妳類的行為有更多的要求。而該行為卻與妳類的核心功能沒有半毛錢關系。也有可能是類的唯壹壹種實現,好比在壹個選項卡的面板獲取另壹個選項卡的外部數據時,將這個選項卡面板中的內容變灰。妳可能想把這些功能放在類的裏面,但是它們不屬於那裏。選項卡條的責任在於管理選項卡。動畫和獲取數據是完全不同的兩碼事,也必須與選項卡條的代碼分開。唯壹壹個讓妳的選項卡條不過時而又將那些額外的功能排除在外的方法是,允許將行為嵌入到代碼當中。換句話說,通過創建事件,讓它們在妳的代碼中與關鍵時刻掛鉤,例如onTabChange, afterTabChange, onShowPanel, afterShowPanel等等。那樣的話,他們可以輕易地與妳的onShowPanel事件掛鉤,寫壹個將面板內容變灰的處理器,這樣就皆大歡喜了。JavaScript庫讓妳可以足夠容易地做到這壹點,但是妳自己寫也不那麽難。下面是使用YUI 3的壹個例子。
<script type="text/javascript" src="/combo?3.2.0/build/yui/yui-min.js"></script>
<script type="text/javascript">
YUI().use('event', function (Y) {
function TabStrip() {
this.showPanel = function () {
this.fire('onShowPanel');
// 展現面板的代碼
this.fire('afterShowPanel');
};
};
// 讓TabStrip有能力激發常用事件
Y.augment(TabStrip, Y.EventTarget);
var ts = new TabStrip();
// 給TabStrip的這個實例創建常用時間處理器
ts.on('onShowPanel', function () {
//在展示面板之前要做的事
});
ts.on('onShowPanel', function () {
//在展示面板之前要做的其他事
});
ts.on('afterShowPanel', function () {
//在展示面板之後要做的事
});
ts.showPanel();
});
</script>這個例子有壹個簡單的 TabStrip 類,其中有個showPanel方法。這個方法激發兩個事件,onShowPanel和afterShowPanel。這個能力是通過用Y.EventTarget擴大類來實現的。壹旦做成,我們就實例化了壹個TabStrip對象,並將壹堆處理器都分配給它。這是用來處理實例的唯壹行為而又能避免混亂當前類的常用代碼。
總結
如果妳打算重用代碼,無論是在同壹網頁,同壹網站還是跨項目操作,考慮壹下在類裏面將其打包和組織起來。面向對象JavaScript很自然地幫助實現更好的代碼組織以及代碼重用。除此以外,有點遠見的妳可以確保代碼具有足夠的靈活性,可以在妳寫完代碼後持續使用很長時間。編寫可重用的不過時JavaScript代碼可以節省妳,妳的團隊還有妳公司的時間和金錢。這絕對能讓妳大受歡迎。