轉(zhuǎn)自 : http://www.bjcan.com/hengxing/readlou.asp?id=1162
八、JavaScript面向?qū)ο蟮闹С?br />~~~~~~~~~~~~~~~~~~
很少有人對JavaScript的面向?qū)ο筇匦赃M行系統(tǒng)的分析。我希望接下來的文字讓你了解到這
個語言最少為人知的一面。
1. JavaScript中的類型
--------
雖然JavaScript是一個基于對象的語言,但對象(Object)在JavaScript中不是第一型的。JS
是以函數(shù)(Function)為第一型的語言。這樣說,不但是因為JS中的函數(shù)具有高級語言中的函
數(shù)的各種特性,而且也因為在JS中,Object也是由函數(shù)來實現(xiàn)的。——關于這一點,可以在
后文中“構造與析構”部分看到更進一步的說明。
JS中是弱類型的,他的內(nèi)置類型簡單而且清晰:
---------------------------------------------------------
undefined : 未定義
number??? : 數(shù)字
boolean?? : 布爾值
string??? : 字符串
function? : 函數(shù)
object??? : 對象
?1). undefined類型
========================
在IE5及以下版本中,除了直接賦值和typeof()之外,其它任何對undefined的操作都將導致
異常。如果需要知道一個變量是否是undefined,只能采用typeof()的方法:
<script>
var v;
if (typeof(v) == 'undefined') {
? // ...
}
</script>
但是在IE5.5及以上版本中,undefined是一個已實現(xiàn)的系統(tǒng)保留字。因此可以用undefined來
比較和運算。檢測一個值是否是undefined的更簡單方法可以是:
<script>
var v;
if (v === undefined) {
? // ...
}
</script>
因此為了使得核心代碼能(部分地)兼容IE5及早期版本,Romo核心單元中有一行代碼用來
“聲明”一個undefined值:
//---------------------------------------------------------
// code from Qomolangma, in JSEnhance.js
//---------------------------------------------------------
var undefined = void null;
這一行代碼還有一點是需要說明的,就是void語句的應用。void表明“執(zhí)行其后的語句,且
忽略返回值”。因此在void之后可以出現(xiàn)能被執(zhí)行的任何“單個”語句。而執(zhí)行的結(jié)果就是
undefined。當然,如果你愿意,你也可以用下面的代碼之一“定義undefined”。
//---------------------------------------------------------
// 1. 較復雜的方法,利用一個匿名的空函數(shù)執(zhí)行的返回
//---------------------------------------------------------
var undefined = function(){}();
//---------------------------------------------------------
// 2. 代碼更簡潔,但不易懂的方法
//---------------------------------------------------------
var undefined = void 0;
void也能像函數(shù)一樣使用,因此void(0)也是合法的。有些時候,一些復雜的語句可能不能
使用void的關鍵字形式,而必須要使用void的函數(shù)形式。例如:
//---------------------------------------------------------
// 必須使用void()形式的復雜表達式
//---------------------------------------------------------
void(i=1);?????? // 或如下語句:
void(i=1, i++);
?2). number類型
========================
JavaScript中總是處理浮點數(shù),因此它沒有象Delphi中的MaxInt這樣的常量,反而是有這
樣兩個常值定義:
? Number.MAX_VALUE? : 返回 JScript 能表達的最大的數(shù)。約等于 1.79E+308。
? Number.MIN_VALUE? : 返回 JScript 最接近0的數(shù)。約等于 2.22E-308。
因為沒有整型的緣故,因此在一些關于CSS和DOM屬性的運算中,如果你期望取值為整數(shù)2,
你可能會得到字符串“2.0”——或者類似于此的一些情況。這種情況下,你可能需要用
到全局對象(Gobal)的parseInt()方法。
全局對象(Gobal)中還有兩個屬性與number類型的運算有關:
? NaN????? : 算術表達式的運算結(jié)果不是數(shù)字,則返回NaN值。
? Infinity : 比MAX_VALUE更大的數(shù)。
如果一個值是NaN,那么他可以通過全局對象(Gobal)的isNaN()方法來檢測。然而兩個NaN
值之間不是互等的。如下例:
//---------------------------------------------------------
// NaN的運算與檢測
//---------------------------------------------------------
var
? v1 = 10 * 'a';
? v2 = 10 * 'a';
document.writeln(isNaN(v1));
document.writeln(isNaN(v2));
document.writeln(v1 == v2);
全局對象(Gobal)的Infinity表示比最大的數(shù) (Number.MAX_VALUE) 更大的值。在JS中,
它在數(shù)學運算時的價值與正無窮是一樣的。——在一些實用技巧中,它也可以用來做一
個數(shù)組序列的邊界檢測。
Infinity在Number對象中被定義為POSITIVE_INFINITY。此外,負無窮也在Number中被定
義:
? Number.POSITIVE_INFINITY? : 比最大正數(shù)(Number.MAX_VALUE)更大的值。正無窮。
? Number.NEGATIVE_INFINITY? : 比最小負數(shù)(-Number.MAX_VALUE)更小的值。負無窮。
與NaN不同的是,兩個Infinity(或-Infinity)之間是互等的。如下例:
//---------------------------------------------------------
// Infinity的運算與檢測
//---------------------------------------------------------
var
? v1 = Number.MAX_VALUE * 2;
? v2 = Number.MAX_VALUE * 3;
document.writeln(v1);
document.writeln(v2);
document.writeln(v1 == v2);
在Global中其它與number類型相關的方法有:
?isFinite()?? : 如果值是NaN/正無窮/負無窮,返回false,否則返回true。
?parseFloat() : 從字符串(的前綴部分)取一個浮點數(shù)。不成功則返回NaN。
?3). boolean類型
========================
?(略)
?4). string類型
========================
JavaScript中的String類型原本沒有什么特殊的,但是JavaScript為了適應
“瀏覽器實現(xiàn)的超文本環(huán)境”,因此它具有一些奇怪的方法。例如:
? link() : 把一個有HREF屬性的超鏈接標簽<A>放在String對象中的文本兩端。
? big()? : 把一對<big>標簽放在String對象中的文本兩端。
以下方法與此類同:
? anchor()
? blink()
? bold()
? fixed()
? fontcolor()
? fontsize()
? italics()
? small()
? strike()
? sub()
? sup()
除此之外,string的主要復雜性來自于在JavaScript中無所不在的toString()
方法。這也是JavaScript為瀏覽器環(huán)境而提供的一個很重要的方法。例如我們
聲明一個對象,但是要用document.writeln()來輸出它,在IE中會顯示什么呢?
下例說明這個問題:
//---------------------------------------------------------
// toString()的應用
//---------------------------------------------------------
var
? s = new Object();
s.v1 = 'hi,';
s.v2 = 'test!';
document.writeln(s);
document.writeln(s.toString());
s.toString = function() {
? return s.v1 + s.v2;
}
document.writeln(s);
在這個例子中,我們看到,當一個對象沒有重新聲明(覆蓋)自己toString()方
法的時候,那么它作為字符串型態(tài)使用時(例如被writeln),就會調(diào)用Java Script
環(huán)境缺省的toString()。反過來,你也可以重新定義JavaScript理解這個對象
的方法。
很多JavaScript框架,在實現(xiàn)“模板”機制的時候,就利用了這個特性。例如
他們用這樣定義一個FontElement對象:
//---------------------------------------------------------
// 利用toString()實現(xiàn)模板機制的簡單原理
//---------------------------------------------------------
function FontElement(innerHTML) {
? this.face = '宋體';
? this.color = 'red';
? // more...
? var ctx = innerHTML;
? this.toString = function() {
??? return '<Font FACE="' + this.face + '" COLOR="' + this.color + '">'
????? + ctx
????? + '</FONT>';
? }
}
var obj = new FontElement('這是一個測試。');
// 留意下面這行代碼的寫法
document.writeln(obj);
?5). function類型
========================
javascript函數(shù)具有很多特性,除了面向?qū)ο蟮牟糠种?這在后面講述),它自
已的一些獨特特性應用也很廣泛。
首先javascript中的每個函數(shù),在調(diào)用過程中可以執(zhí)有一個arguments對象。這個
對象是由腳本解釋環(huán)境創(chuàng)建的,你沒有別的方法來自己創(chuàng)建一個arguments對象。
arguments可以看成一個數(shù)組:它有l(wèi)ength屬性,并可以通過arguments[n]的方式
來訪問每一個參數(shù)。然而它最重要的,卻是可以通過 callee 屬性來得到正在執(zhí)行
的函數(shù)對象的引用。
接下的問題變得很有趣:Function對象有一個 caller 屬性,指向正在調(diào)用當前
函數(shù)的父函數(shù)對象的引用。
——我們已經(jīng)看到,我們可以在JavaScript里面,通過callee/caller來遍歷執(zhí)行
期的調(diào)用棧。由于arguments事實上也是Function的一個屬性,因此我們事實上也
能遍歷執(zhí)行期調(diào)用棧上的每一個函數(shù)的參數(shù)。下面的代碼是一個簡單的示例:
//---------------------------------------------------------
// 調(diào)用棧的遍歷
//---------------------------------------------------------
function foo1(v1, v2) {
? foo2(v1 * 100);
}
function foo2(v1) {
? foo3(v1 * 200);
}
function foo3(v1) {
? var foo = arguments.callee;
? while (foo && (foo != window)) {
??? document.writeln('調(diào)用參數(shù):<br>', '---------------<br>');
??? var args = foo.arguments, argn = args.length;
??? for (var i=0; i<argn; i++) {
????? document.writeln('args[', i, ']: ', args[i], '<br>');
??? }
??? document.writeln('<br>');
??? // 上一級
??? foo = foo.caller;
? }
}
// 運行測試
foo1(1, 2);
2. JavaScript面向?qū)ο蟮闹С?br />--------
在前面的例子中其實已經(jīng)講到了object類型的“類型聲明”與“實例創(chuàng)建”。
在JavaScript中,我們需要通過一個函數(shù)來聲明自己的object類型:
//---------------------------------------------------------
// JavaScript中對象的類型聲明的形式代碼
// (以后的文檔中,“對象名”通常用MyObject來替代)
//---------------------------------------------------------
function 對象名(參數(shù)表) {
? this.屬性 = 初始值;
? this.方法 = function(方法參數(shù)表) {
??? // 方法實現(xiàn)代碼
? }
}
然后,我們可以通過這樣的代碼來創(chuàng)建這個對象類型的一個實例:
//---------------------------------------------------------
// 創(chuàng)建實例的形式代碼
// (以后的文檔中,“實例變量名”通常用obj來替代)
//---------------------------------------------------------
var 實例變量名 = new 對象名(參數(shù)表);
接下來我們來看“對象”在JavaScript中的一些具體實現(xiàn)和奇怪特性。
?1). 函數(shù)在JavaScript的面向?qū)ο髾C制中的五重身份
?------
“對象名”——如MyObject()——這個函數(shù)充當了以下語言角色:
? (1) 普通函數(shù)
? (2) 類型聲明
? (3) 類型的實現(xiàn)
? (4) 類引用
? (5) 對象的構造函數(shù)
一些程序員(例如Delphi程序員)習慣于類型聲明與實現(xiàn)分開。例如在delphi
中,Interface節(jié)用于聲明類型或者變量,而implementation節(jié)用于書寫類型
的實現(xiàn)代碼,或者一些用于執(zhí)行的函數(shù)、代碼流程。
但在JavaScript中,類型的聲明與實現(xiàn)是混在一起的。一個對象的類型(類)
通過函數(shù)來聲明,this.xxxx表明了該對象可具有的屬性或者方法。
這個函數(shù)的同時也是“類引用”。在JavaScript,如果你需要識別一個對象
的具體型別,你需要執(zhí)有一個“類引用”。——當然,也就是這個函數(shù)的名
字。instanceof 運算符就用于識別實例的類型,我們來看一下它的應用:
//---------------------------------------------------------
// JavaScript中對象的類型識別
//?? 語法:? 對象實例 instanceof 類引用
//---------------------------------------------------------
function MyObject() {
? this.data = 'test data';
}
// 這里MyObject()作為構造函數(shù)使用
var obj = new MyObject();
var arr = new Array();
// 這里MyObject作為類引用使用
document.writeln(obj instanceof MyObject);
document.writeln(arr instanceof MyObject);
================
(未完待續(xù))
================
接下來的內(nèi)容:
2. JavaScript面向?qū)ο蟮闹С?br />--------
?2). 反射機制在JavaScript中的實現(xiàn)
?3). this與with關鍵字的使用
?4). 使用in關鍵字的運算
?5). 使用instanceof關鍵字的運算
?6). 其它與面向?qū)ο笙嚓P的關鍵字
3. 構造與析構
4. 實例和實例引用
5. 原型問題
6. 函數(shù)的上下文環(huán)境
7. 對象的類型檢查問題
?