Javascript基本概念
JavaScript是一種基於對象(Object)和事件驅動(Event Driven)並具有安全效能的指令碼語言,最初由Netscape公司創造出來,起名Live Script,它和Java的關係只有一個:名字比較像。使用它的目的是與HTML超文字標記語言 (HTML)、Java 指令碼語言(Java小程式)一起實現在一個Web頁面中連結多個對象,與Web客戶互動作用。從而可以開發用戶端的應用程式等。它是通過嵌入或調入在標準的HTML語言中實現的。它的出現彌補了HTML語言的缺陷,它是Java與HTML折衷的選擇。(注意,如同VBScript一樣,JavaScript一樣有伺服器端版本)
一、JavaScript的基本文法
0、引言
Javascript的文法基本上與Java一致,但是由於Javascript是一個弱類型的指令碼語言,在程式編寫的過程中會有一些不同。同時由於Javascript是基於對象的語言,注意不是物件導向的語言,所以它在對對象的支援上有一定缺陷,大家所熟悉的諸如對象繼承,多態等物件導向語言所具有的基本特性在Javascript中只能通過一些變通手段來實現(通常比較複雜)。然而,弱類型語言也有其優點,那就是簡單性,Javascript中類型轉化是非常方便的(弱類型語言在代碼中基本上表現為無類型),一個String通過一個簡單的加減操作就可以轉化為Int(相當於調用了Integer.ParseInt(String)),而且不會拋異常。Javascript作為一種解釋性語言,還能使用在編譯性語言C/C++、JAVA難以支援的eval語句。由於運行在沙箱中,Javascript運行時有很多的安全性限制。它不允許訪問本地的硬碟,並不能將資料存入到伺服器上,不允許對網路文檔進行修改和刪除,只能通過瀏覽器實現資訊瀏覽或動態互動,從而有效地防止資料的丟失。 總體上來說,Javascript應該是優缺點兼備(辨證的說=])。
作為學過JAVA的軟院本科生來說,學習Javascript並不困難。Javascript不像HTML、CSS這種經驗性很強的的領域,一旦大家入門之後,剩餘階段的Javascript相關的學習很可能就是查閱資料而已。在這裡我希望我所寫的內容能夠起到拋磚引玉的作用,為大家打下基礎。以下內容主要是Javascript的入門知識,我會從關鍵字開始描述,強調關鍵字是為了讓大家對Javascript的文法有比較全面的認識,可能大家在今後的開發中可能一直用不到其中的某些關鍵字,但我認為大家有必要瞭解一下,同時請留意其中標出的注意事項。隨後將是在頁面中加入指令碼的四種方法。在“Javascript用戶端編程”中,我將提及瀏覽器文檔(DOM)模型和事件(EVENT)模型,其中會有如何尋找對象以及安裝事件處理器(事件處理映射)的詳細解說。最後我將在“Javascript範例”中給出一段核心代碼的註解和三個範例。“Javascript學習資料”中有一些有用的書籍名,電子參考資料和相關網址,請留意其中的推薦參考資料和MLParser的使用指南。大家的對Javascript問題我將會在FAQ中給出解答。
第一次寫,錯誤在所難免,懇請大家指正和諒解。
1、VAR
var i = 5;
var j = "hello world";
var k = document;
for( var u = 0; ... ; ... ) { ... }
function fun() { var v = document; ... }
VAR的作用是聲明變數,但是不通過VAR聲明的變數也可以使用。
在BLOCK塊(用 { 和 } 或 (和 )括起來的的程式碼片段)中用VAR聲明的變數是局部變數,一旦出了BLOCK的範圍(SCOPE),變數就會失效。例如在範例中的 u 和 v 變數,當程式流分別出了 FOR 和 FUNCTION 語句的範圍之後 u 和 v 就成了未定義變數。
其他情況下用VAR聲明或者通過未聲明直接使用的變數(解譯器會隱式聲明這些變數)都是全域變數。
在同一個範圍(SCOPE)中對同一個變數名不能用一次以上的VAR,即不可重複聲明變數。
不同範圍(SCOPE)中聲明的同名變數在Javascript中會相互隱藏,例如,有一個全域變數 variable,同時在程式段中還有一個局部變數 variable,那麼你在程式中引用的變數實際上會是局部變數 variable 。
一個變數在賦值之後,其類型就轉化為所賦值的類型。
從未聲明過(包括賦值操作引發的隱式聲明)的變數值為 undefined 類型也為 undefined 。
變數的有效性是它的定義範圍與定義語句出現的順序無關。
function test(){
document.write(great) ; // print "undefined"
document.write(odd) ; // print "javas" , not "undefined"
var odd = "javas" ;
}
範例中雖然 odd 在 document.write 之後,但在程式被解釋時 odd 就被初始化了,所以列印結果不是 "undefined" 而是odd被賦予的初始值。
2、IF-ELSE
if( val > 2.3){
rs = 5.56;
}
else if( val + rs > "1.2") {
rs = document;
}
else{
rs = "Hello world";
}
IF-ELSE的用法與JAVA中的完全相同。
注意運算式中的中的“val + rs > '1.2' "這在JAVA中是不允許出現的。
另外雖然Javascript不要求在每句語句之後加分號,但加上分號是良好的編程習慣。
在範例中出現的未聲明就使用的變數的情況在Javascript中是允許的,他們將自動轉化為全域變數。
Javascript是大小寫敏感的,所以請注意關鍵字的大小寫。
3、SWITCH
switch(key - 65){
case 0:
ch = "A" ;
break;
case 1:
ch = "B" ;
break;
default:
ch = "X" ;
break;
case 9:
ch = "Y" ;
break;
}
SWITCH的用法與JAVA中的完全相同。
CASE後跟的變數建議使用常量運算式(整數和字串),不要用浮點。
每個CASE結束時的BREAK語句通常不可少,除非想利用SWITCH的FALL-THROUGH來實現特定的功能。
DEFAULT語句可以放在SWITCH結構中的任意位置,可以於CASE語句交叉放置。
4、WHILE
while( i < 0 && bool_var){
if( i > -5)
continue;
i += 3 + i;
}
WHILE的用法與JAVA中的完全相同。
如果是BOOL變數可以不寫bool_var == true/false,直接使用即可。
CONTINE語句會使程式流跳過迴圈中剩餘的語句,進入迴圈的下一次迭代。
在Javascript中也有帶LABEL的BREAK和CONTINUE,用法與JAVA相同。
在寫迴圈時,注意不要產生“死”迴圈。範例程式片斷中的就是一個“死”迴圈。
5、DO-WHILE
do{
i -= 8;
} while( i > 0);
DO-WHILE的用法與JAVA中的完全相同。
不要遺漏結尾WHILE(Expression)之後的分號。
6、FOR
for (var i = 0; i < 8; i++){
document.writeln("Hello world !");
}
DO-WHILE的用法與JAVA中的完全相同。
不要在計數變數 i 之前加 int 類型標識符,Javascript是弱類型語言,加了 int 反倒會報文法錯,但是可以用 var 使之成為局部變數。
FOR(... ; ... ; ...)中分號之間的內容都可以空缺(for (;;)相當於while(true)),其中也可以使用多句語句用逗號分隔。
7、FOR-IN
for ( var ite in document) {
str_result += document [ ite ];
}
FOR-IN控制語句在JAVA中不存在,它的作用有點類似JAVA中的 Iterator 介面描述的功能。在範例中,ite將遍曆 docment 中所有的可遍曆元素(不是所有元素),每次迭代時,ite中會包含被遍曆數組或對象的索引字串(可以看作對象名),例如,textfield(如果你在頁面中有一個元素的ID為textfield),或者像數字1、2、3(它們用來引用對象中的未命名元素)。
引用對象中元素時使用關聯陣列的方式:數組名或對象名 [ 索引值 ],例子中用 document [ ite ] 表示 document 中索引為 ite 的元素。
使用FOR-IN的最大好處就是你不需要知道目標對象或者數組究竟有多少元素,以及其內部結構是怎麼樣的,就可以遍曆其所有可遍曆元素。
8、CONTINUE-BREAK
again:
while ( test() ){
whie (is_run) {
if(work()) {
break again;
// continue again;
}
reap();
}
i++;
}
CONTINUE-BREAK的用法與JAVA中的完全相同。
使用帶Label的break或者continue可以在內外迴圈中進行跳轉。
9、FUNCTION
function fun_1(arg1, arg2) {
rs = arg1 + arg2;
return rs;
}
FUNCTION在Javascript中的寫法與JAVA中的有很大的差異。
首先是參數類型的問題,參數前面不需要加任何類型描述,VAR也不能加。Javascript方法參數也有傳值和傳引用之分,規則與JAVA基本一致,具體請查閱相關資料。
其次是傳回值,傳回值的類型不需要標明,RETURN會返回相應的對象,若無RETURN的資料,則傳回值為undefined。從這個意義上講,FUNCTION總是有傳回值的。
最後是參數個數的問題,參數列表並不限制實際傳入函數的參數個數,它只是提供了一個訪問參數的捷徑,也就是說給了特定位置參數一個特定的名字。
sum = fun_1(1) ;
以上函數調用時只傳給 fun_1 一個參數( fun_1 定義時需要兩個參數)。那麼此時 arg2 的值是什麼呢?undefined,你猜對了。
我們可以把 fun_1 改成以下形式來應對這種情況。
function fun_2(arg1, arg2) {
if ( !arg1 ) arg1 = 0;
if ( !arg2 ) arg2 = 0;
rs = arg1 + arg2;
return rs;
}
undefined在布林運算式中相當於 false 。
好了,問題似乎解決了。可是如果我們要處理更多參數怎麼辦呢?例如以下函數調用所代表的情況。
sum = fun_2(1, 2, 3) ;
在函數內部有一個Arguments對象,它是一個參數數組,通過它可以訪問到傳入函數的所有參數。
根據這一特性我們把 fun_2 改成 fun_3。
function fun_3 () {
rs = 0;
for (var i = 0 ; i < Arguments.length; i++) {
rs += parseInt( Arguments[i] );
}
return rs;
}
注意:這裡使用了parseInt而不是直接加法引起的隱式轉化。這是因為隱式轉化的要求過高,而且有可能把 rs 轉化為其他內部類型。
0 + "23a" = NaN;0 + parseInt ( "23a" )= 23
function Point ( x, y ) {
this.x = x;
this.y = y;
this.func = m_func;
}
function m_func( num ) { ... }
var newPoint = new Point( 1, 3 );
newPoint.func( newPoint.x + new Point.y);
任何一個函數都可以成為建構函式,在函數中的 this 關鍵字同JAVA中意義類似,但不完全相同。
通過 new 產生的對象最終會通過記憶體回收機制清除。
函數也是Javascript的內部類型之一,所以可以賦給某個變數,注意不要加 () ,()實際上也是一個操作符表示對函數的調用。
this.func = m_func; 表示把m_func函數賦給 this 的 func 成員變數。
this.func = m_func(); 表示把m_func函數調用的傳回值賦給 this 的 func 成員變數。
對象成員訪問與JAVA類似:對象名.成員名
為一個類添加新成員,只要給特定的成員名賦值即可(不賦值的話讀出來都是 undefined),實際上全域變數或函數也就是頂級對象的成員屬性和方法,從這個角度上來思考,大家就很容易理解我在VAR一節中描述的變數聲明規則了。
在Javascript中函數既然被視作一個類型,那麼函數的聲明就會有與普通變數相似的方法:
var my_func = new Function ("arg1", "arg2", ... , "argN", " var rs = arg1 + arg2 + ... + argN; return rs; ");
var my_func = function (arg1, arg2, ... , argN)
{
var rs = arg1 + arg2 + ... + argN;
return rs;
};
前者被稱之為構造器法,後者被稱之為直接量法。
10、PROTOTYPE
function Point ( x, y ) {
this.x = x;
this.y = y;
// this.func = m_func;
}
Point.prototype.func = m_func;
Point.prototype.s_name = "Point";
function m_func( num ) { ... }
new Point () ;
var newPoint = new Point( 1, 3 );
newPoint.func( newPoint.x + new Point.y);
PROTOTYPE是原型的意思,我改變在第九節中 Point 的實現。把 m_func 賦給了Point的原型。
這一改變唯一的好處就是我們不用在每次調用 Point 函數都對 func 屬性賦值了,func 屬性被儲存在 Point 的原型對象中,從而節省了記憶體空間。 Point 與 Point.prototype 的關係請查閱相關的資料,這裡不再詳述。
用PROTOTYPE可以實現JAVA中的靜態變數和靜態方法(由於某些瀏覽器實現在對象建立之後才建立它的原型對象,所以建議在使用原型對象中靜態成員之前先調用一次構造器方法,如同範例中 new Point();語句,調用結束之後,無用對象將被回收,但其原型對象將繼續駐留在記憶體中),在Javascript支援的有限的對象繼承也與PROTOTYPE有一定聯絡。
11、ARRAY
var arr_8 = new Array(8);
var arr = new Array();
var arr_d = [ 1, 2, , 4 , .., "Hi", null, true, document ];
var arr_m = [ [ 1, 2, 3, 4 ], [ 5, 6, 7], [ 8 ] ];
arr_8[ 3 ] = "ABC";
arr[ 100 ] = 8.8888;
arr_d[ 0 ] = "CDE";
arr_m[ 1 ][ 0 ] = "XYZ";
數組的建立可以通過 new Array 的構造器方法(參數是數組初始長度,空參數表示零長度)。
或者是把[ 資料 , 資料 , ... , 資料]的數組直接量賦給變數,資料之間用逗號分隔,arr_d中藍色的部分有兩個連續的逗號表示第三個元素空缺,其值為 undefined。
構造器方法的範例: arr_8和arr ;數組直接量的範例: arr_d和arr_m 。
Javascript中的數組是動態數組,它將隨著元素數量自動調節數組長度。
Javascript中的數組元素沒有任何類型限制,未初始化的元素值為 undefined。
Javascript中的多維陣列的實現與JAVA中的完全相同。arr_m中 [ 1, 2, 3, 4] 表示 arr_m[0]所指向的第二維數組的資料。
Javascript對數組的訪問與JAVA中的完全相同。
var textfield = document.all[ "textfield" ];
document.all 是一個數組嗎?不完全是。
那為什麼我們可以用 “textfield” 來訪問我們的對象呢?
這是因為以上我們所看到的是Javascript中非常特殊的用法——關聯陣列,也可以稱之為索引器。
對象名[ "成員名" ] = 對象名.成員名
關聯陣列的使用,可以使某些操作從寫入程式碼中解脫出來,使之更具有靈活性。請看下面一個例子。
假如我們在執行某個與對象相關的操作時需要靠外界輸出才能確定調用的函數。
方案之一:SWITCH,每更改一個分支就需要更新該方法。
方案之二:對象 + . + 函數名();,語法錯誤。
方案之三:對象 [ 函數名字串 ]();,好的。
方案之四:eval(對象名字串 + "." + 函數名字串 + "();");,也可以的。
關聯陣列的使用,使我們能夠用字串,數字或者其他的類型的變數作為索引來訪問我們所需要訪問的屬性和方法。
在FOR-EACH中常常會用這種方法來遍曆對象或數組屬性和方法。
12、UNDEFINDED-NULL
undefined == null ? true
undefined === null ? false
undefined 表示所引用的對象未經定義。
null表示所引用的對象的值是空值。
在布林運算式中它的作用基本與null一致,都表示 false。
13、TRY-CATCH-FINALLY-THROW
try{
throw new Error( "Test Exception" ) ;
}
catch( e ){
document.writeln( e.name + ":" + e.message);
}
finally{
document.writeln( "Final here");
}
TRY-CATCH-FINALLY-THROW的用法與JAVA中的完全相同。
這是Javascript 1.5才有的新特性,在早期的瀏覽器中可能不支援。目前常用的瀏覽器 IE6、NAV7、Opera、FireFox 1.0 都支援。
14、WITH
function Point ( x, y ) {
this.x = x;
this.y = y;
}
var newPoint = new Point( 1, 3 );
with (newPoint) {
var sum = x + y;
}
WITH的用法與DELPH中的完全相同。
由於使用了WITH,在其範圍中newPoint.x 和 newPoint.y 分別被簡化為了 x 和 y 。
15、TYPEOF
swich (typeof obj) {
case "String" :
rs = "string" ;
break;
case "Object" :
rs = "object" ;
break;
case "Number" :
rs = "Number" ;
break;
defualt:
rs = "Unknown Type" ;
}
TYPEOF的作用是返回變數中資料類型對應的字串。
TYPEOF返回的字串隨著瀏覽器的不同會有一定的差異。
二、在網頁中使用JavaScript
1、連結標記的URL中
<a href = "Javascript: alert('Hi !');" >Click Me </a>
Click Me
這種做法通常只在教學演試中使用。
HREF中的"Javascript : // "的協議頭一定要加,其中可以寫多句指令碼,但不能寫 RETURN 語句。
2、HTML標籤的事件處理屬性中
<a href = "#" onclick = "Javascript: alert('Hello !');return false;">Click Me Too</a>
Click Me Too
這種做法比較簡單,比較常用。return false 是為了禁止頁面跳轉。
通常 "Javascript : // "的協議頭可以不加,簡寫為 onclick = "alert('Hello !');return false;"。
3、頁面的SCRIPT標籤中
<script language="javascript" type="text/javascript">
<!--//--><![CDATA[//><!--
function testJs(){
alert('Hello !');
...
}
//--><!]]>
</script>
...
<a href = "#" onclick = " testJs();return false;">Click Me Again</a>
Click Me Again
這種做法把指令碼與HTML做了一定的分離,代碼的整體結構比較良好。
在代碼的周圍加上<!--//--><![CDATA[//><!-- 和 //--><!]]>是為了避免不支援指令碼的瀏覽器把指令碼當作普通文本輸出。
與之作用類似的還有<noscript>標籤,<noscript>標籤在瀏覽器不支援指令碼時顯示出其中的提示性文字。
<script>標籤通常都放在<head>標籤內。
4、外部指令檔中
[ testJs.js ]
<!--//--><![CDATA[//><!--
function testJsExt(){
alert('Ohhh No!');
...
}
//--><!]]>
[ *.htm ]
<script language="javascript" type="text/javascript" src="mat/js/testJs.js"></script>
...
<a href = "#" onclick ="testJsExt();return false;">Click Me Nowww! </a>
Click Me Nowww !
外部指令碼就是把指令碼儲存在一個單獨的 *.js 檔案中,通過指定<script>標籤的 src 屬性,把指令碼引入。
效果相當於在原先的<script> 標籤中間插入外部檔案中的指令碼文本。
注意某些瀏覽器將忽略有SRC屬性的<script>標籤中的指令碼代碼。
這種方法從本質上來講與第三種方法沒有差別,但是由於把指令碼和HTML做了完全的分離,所以是商業領域最常用的方法。
現在我們在標籤<a>中仍然有Javascript的痕迹,在Javascript用戶端編程中我將會介紹如何將其去除,以及使Javascript指令碼在HTML中留下最少痕迹的手段