JavaScript面向對象編程
1、引言
JavaScript是一種解釋性的,基于對象的腳本語言(an interpreted, object-based scripting language)。JavaScript 基于客戶端運行,目前基本上所有的瀏覽器都支持JavaScript。1995年首次出現時,JavaScript的主要目的還只是處理一些輸入的有效性驗證,隨著互聯網的蓬勃發展,JavaScript的應用越來越廣泛,特別是近幾年AJAX技術(Asynchronous JavaScript and XML)的發展,更使JavaScript的應用達到了一個新的高度。在AJAX技術中,JavaScript是一項關鍵技術,請求的發送、接收、接收數據后的界面處理都需要使用JavaScript技術,這對JavaScript語言提出了新的需求,本文從JavaScript的基本特點出發,模擬出了面向對象編程的大部分特點,使JavaScript擺脫了以往腳本語言雜亂無章、難以閱讀、難以維護的形象,而具有了面向對象特性,極大的方便了JavaScript的開發、維護,提高了軟件開發效率。
2、JavaScript的基本特點
JavaScript是解釋性的,基于對象的腳本語言。它有下面幾個顯著特點,這幾個特點在后面的面向對象特性模擬中會反復用到,因此這里先詳細說明這幾個特點。
l 解釋型語言:JavaScript是一種解釋性語言,解釋性語言相對于編譯型語言,編譯型語言必須先通過編譯才能執行,而解釋性語言不需要編譯,直接從上到下解釋執行,一邊解釋一邊執行,這就決定了解釋性語言的代碼是有先后順序的,需要執行的代碼必須已經解釋過。因此,JavaScript需要注意代碼的先后順序。
l 弱類型語言:JavaScript 是一種弱類型語言,弱類型語言相對于強類型語言,大部分面向對象語言都是強類型語言,強類型語言是一種需要強制類型定義的語言,它要求每個變量都確定某一種類型,它和別的類型轉換必須顯式轉換。弱類型語言是一種類型可以被忽略的語言,它在變量定義時不指定某一類型,在執行時通過執行結果才能確定類型,不同類型之間不需要通過顯式轉換就可以轉換。
l 動態添加屬性和方法:這個特點是指可以動態為某個對象添加以前沒有的屬性和方法。這個特點使JavaScript非常靈活,正因為有了這個特點,JavaScript的面向對象編程才有了可能。
l prototype(原型)屬性:JavaScript是一種基于對象的語言,JavaScript中的所有對象,都具有prototype屬性。prototype屬性返回對象的所有屬性和方法,所有 JavaScript 內部對象都有只讀的 prototype 屬性,可以向其原型中動態添加屬性和方法,但該對象不能被賦予不同的原型。但是自定義的對象可以被賦給新的原型。
3、面向對象的基本特點
面向對象有下列三個主要特點:封裝、繼承和多態。這里先詳細說明這幾個特點,后面幾個部分分別在JavaScript中實現這些特點,從而實現完整的面向對象模擬。
l 封裝:封裝就是把各種方法和變量合并到一個類,用這個類代表某個對象為完成一定的任務所能保存的范圍以及它能執行的操作。封裝隱藏了方法執行的細節。
l 繼承:繼承就是根據現有類的方法和成員變量生成新的類的功能。
l 多態:多態就是對象隨著程序執行而使其形式發生改變的能力。
4、JavaScript語言基礎
4.1數據類型
基本數據類型:Number, String, Boolean, Function, Object, Array, null, undefined,注意null和undefined的區別。
日期和時間:日期類型并不是JavaScript的基本數據類型,但JavaScript提供了一個處理日期的類:Date,用法如下:
var now = new Date();
var year = now.getYear(); // 年
var month = now.getMonth() + 1; // 月
var day = now.getDate(); // 日
var hour = now.getHours(); // 時
var minute = now.getMinutes(); // 分
var second = now.getSeconds(); // 秒
alert("現在時間是: "+year+"-"+month+"-"+day+" "+hour+":"+minute+":"+second);
</td>
</tr>
</tbody>
</table>
正則表達式:主要用于對文本進行模式匹配,實現對文本的查找和替換操作。在JavaScript中,提供了一個RegExp類來處理正則表達式,創建方式和Date一樣,用關鍵字new就可以創建,如var re = new RegExp();
和Date不一樣的地方在于,雖然RegExp類也不是JavaScript的基本數據類型,但我們在創建正則表達式對象時,可以不需要用new關鍵字就可以創建,如 var re = /[1-9][0-9]*/; 這樣就直接定義了正則表達式對象,因為在JavaScript中,一對斜線中包括一個文本就認為構成了一個正則表達式對象。
下面就示例用正則表達式判斷輸入的Email和手機號是否合法:
// 判斷Email是否合法
function isEmail(p_addr)
{
var reEmail = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/;
return reEmail.test(p_addr);
}
// 判斷手機號是否合法
function isPhoneNumber(p_num)
{
var rePhone = /^1\d{10}$/gi;
return rePhone.test(p_num);
}
// 測試
var email = "tenderghost@163.com";
alert(isEmail(email));
var phone = 137;
alert(isPhoneNumber(phone));
</td>
</tr>
</tbody>
</table>
錯誤對象:JavaScript中定義了幾個用于處理錯誤類型的類,有:Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError。和Java中的異常處理方式類似,JavaScript中的錯誤對象可以用try...catch...finally語句來處理,示例如下:
try{
throw new Error("自定義錯誤示例");
}
catch (ex) {
alert("Error對象被捕捉,消息為: " + ex.message);
}
finally {
alert("無論如何都會被執行!");
}
</td>
</tr>
</tbody>
</table>
4.2 變量
JavaScript是一種弱類型的語言,這就意味著一個JavaScript變量可以指向任何數據類型,例如:
var i = 10;
i = “ten”;
變量的作用域
var scope = “global scope”; // 全局變量
function checkscope() {
var local = “local scope”; // 局部變量
}
注意:除函數中的變量為局部變量外,其他的全部為全局變量。
4.3 函數
JavaScript中:
function add(a, b) {
return a + b;
}
Java中:
public int add(int a, int b) {
return a + b;
}
函數的參數: arguments對象
在一個函數中,會有一個隱含的arguments對象來保存函數的參數,這樣在有些時候,我們在定義函數時,可以不明確指定函數所需要的參數,如下:
// 求最大值
function max() {
var m = Number.NEGATIVE_INFINITY; // 無窮小
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] > m)
m = arguments[i];
}
return m;
}
// 測試
var largest = max(1, 7, 9, 23, 88, 2, 5);
alert(largest);
</td>
</tr>
</tbody>
</table>
5、封裝的實現
下面以一個詳細的示例來說明常用的私有實例成員、公有實例成員和公有靜態成員的封裝。
Human = function(name) // 等于function Human(name)
{
var me = this;
// 私有屬性
var _name = null;
// 公有屬性
me.name = null;
// 私有方法
function setName()
{
_name = name;
me.name = _name;
}
// 公有方法
me.sayHello = function()
{
alert("Hello, my name is " + me.name);
}
// 模擬構造函數
function constructor()
{
setName();
}
constructor();
return me;
}
// 增加類的靜態方法
Human.classMethod = function()
{
alert("Human's classMethod");
}
// 通過原型(prototype)增加公有方法
Human.prototype.sayGoodbye = function()
{
alert("Goodbye, " + this.name);
}
// 當成類來使用
var m_human = new Human("pengfei");
m_human.sayHello();
// 調用類的靜態方法
Human.classMethod();
// 直接當成函數使用
Human("huang");
</td>
</tr>
</tbody>
</table>
JavaScript語言中類的定義和函數的定義都是使用function關鍵字,使用function定義的過程,即可以看成是類的定義,也可以看成是函數的定義。從示例代碼中可以得出:
· 私有屬性和方法的定義,直接在類內部定義一個變量,因為這個變量的作用域只限定在類內部,外部不能使用,因此這樣定義的屬性是私有屬性,私有方法的定義也類似。
· 公有屬性和方法的定義,通過定義一個私有變量me等于this,然后動態添加me變量的屬性和方法,最后把me變量作為創建的實例對象返回。這樣給me變量添加的屬性和方法在類外部可以使用,也就是共有屬性和方法。
· 構造函數的定義,構造函數是在創建一個對象時,自動執行的一個函數。在Java,C#等面向對象的語言中,只要定義一個函數和類名相同即可。在JavaScript中,可以隨便定義一個私有函數,這個函數需要在類定義體中執行,這樣的函數即成為構造函數,需要注意的是,為了確保構造函數中的代碼都已經被解釋過,構造函數最好放在類定義的最后。
· 類靜態方法的定義,類靜態方法是指不需要通過類實例來調用,而是可以直接通過類名來調用的方法。在Java,C#等面向對象語言中,一般是通過關鍵字static來指明一個方法是靜態方法。在JavaScript中,沒有static關鍵字,不能在類的定義體中實現靜態方法,必須在類的定義體外,通過直接在類上動態添加方法來定義靜態方法。需要注意,JavaScript靜態方法中不能訪問類的公有屬性和公有方法,這和Java,C#等語言是一致的。
· 類的公有屬性和公有方法也可以使用prototype來實現, 但是使用prototype有以下幾個注意點:需要定義在類定義體外,和Java等語言的封裝習慣不一致;prototype方式不能訪問類的私有屬性。
· JavaScript不能實現只讀屬性、只寫屬性的定義,所有的公有屬性都是可讀可寫。
6、繼承的實現
JavaScript中沒有Java,.Net中實現繼承的關鍵字,JavaScript中的繼承都是通過JavaScript語言本身的特性模擬出來的,可以通過兩種方式實現繼承:
l 創建對象方式
// 定義父類
Human = function()
{
var me = this;
me.name = "";
me.age = 0;
me.setName = function(name)
{
me.name = name;
}
me.setAge = function(age)
{
me.age = age;
}
me.sayHello = function()
{
alert("Human sayHello, name:"+ me.name +", age:"+ me.age);
}
return me;
}
// 定義子類
Chinese = function(name, age)
{
// 繼承
var me = new Human();
// 覆蓋父類的sayHello方法
me.sayHello = function()
{
alert("中國人問好,名字:"+ me.name +",年齡:"+ me.age);
}
// 設置name和age
me.setName(name);
me.setAge(age);
return me;
}
// 測試
var c = new Chinese("李四", 21);
c.sayHello();
</td>
</tr>
</tbody>
</table>
定義一個變量me,賦予父類實例,這樣me就有了父類的屬性和方法,然后給me增加子類的屬性和方法,最后把me變量作為創建的實例對象返回。這樣定義的類就有了父類的屬性和方法,即實現了繼承。
l 原型(prototype)方式
// 定義父類
function Human()
{
this.name = "";
this.age = 0;
}
Human.prototype =
{
setName : function(name)
{
this.name = name;
},
setAge : function(age)
{
this.age = age;
},
sayHello : function()
{
alert("Human sayHello, name:"+ this.name +", age:"+ this.age);
}
}
// 定義子類
function Chinese(name, age)
{
this.setName(name);
this.setAge(age);
}
// 繼承
Chinese.prototype = new Human();
// 覆蓋父類的sayHello方法
Chinese.prototype.sayHello = function()
{
alert("中國人問好,名字:"+ this.name +",年齡:"+ this.age);
}
// 測試
var c = new Chinese("張三", 20);
c.sayHello();
</td>
</tr>
</tbody>
</table>
首先封裝好子類的屬性和方法,然后創建一個父類實例附給子類的prototype屬性,這樣子類就有了父類的屬性和方法,即實現了繼承。
這兩種方式都實現了繼承,但是和Java,.Net等面向對象語言相關,JavaScript中模擬的繼承還要以下問題:
l 不能定義保護的方法和屬性。
l 不能繼承父類的靜態方法。
7、多態的實現
多態主要包括重載(overload)和覆蓋(override),重載是指同一個名字的函數或方法可以有多個實現,他們依靠參數的類型或參數的個數來區分識
別。而覆蓋是指子類中可以定義與父類中同名的方法,這些方法定義后,在子類的實例化對象中,父類中繼承的這些同名方法將被隱藏。
由于JavaScript的弱類型性,JavaScript在定義函數時,不需要指定函數參數的類型和個數,這種特性為重載的實現提供了便利。如下函數:
function say(param)
{
// 通過typeof函數,判定不同類型的參數。
if (typeof(param)=="string")
alert("string");
else if (typeof(param)=="number")
alert("number");
else
alert("others");
}
</td>
</tr>
</tbody>
</table>
函數調用時可以傳入string,也可以傳入number,或別的類型,在函數實現體中可以使用typeof函數得到傳入參數的類型,從而實現不同的功能。這樣雖然是一個函數實現體,但是也有了多個實現,即重載。JavaScript中另外一種比較常見的重載方式是通過函數的arguments對象來實現,這種方式已在前面講過,不再贅述。
JavaScript中覆蓋的實現,只需在子類中定義一個與父類同名的方法即可。
8、靜態類的實現
靜態類,是一種不能被實例化,并且只包含有靜態成員的類。在Java,C#等面向對象語言中,一般使用static關鍵字來指明類是靜態類,JavaScript中沒有static關鍵字,但是可以通過實例化匿名函數來實現靜態類。如下:
| | | | | | | |