這篇文章里,我們來聊一些JavaScript的基礎知識。
1. 如何運行JavaScript?
JavaScript是一種解釋型的語言,它不需要提前編譯。通常情況下,JavaScript會放在網頁中,在瀏覽器中運行。我們也可以找到一些在線的可以運行JavaScript的平台,當然我們也可以在Firefox或者Chrome里運行。
我在網上找到了一個可以在線運行的網站:runjs,文章中的代碼都是在該網站上運行的。
實際上,我們可以在本地創建一個簡單的網頁,然后通過編輯網頁的方式來測試JavaScript代碼。
下面是代碼的基本運行結構:
1 $(document).ready( 2 function(){ 3 //在這里調用外面的函數 4 print(add(1,1)); 5 }); 6 7 //封裝document.write 8 function print(obj){ 9 document.write(obj); 10 document.write("<br>"); 11 } 12 13 //簡單的示例方法 14 function add(left, right){ 15 return left + right; 16 }
2. JavaScript基本數據類型
JavaScript的基本數據類型包括6種:number/string/boolean/object/function/undefined。
2.1 number類型
number類型用來存儲數值,它描述的是64位的浮點型數值。但Javascript並不能表示0-2e64之間的所有數值,因為它還需要表示非整數,包括復數、分數等。對於64位來說,需要使用11位來存儲數字的小數部分,使用1位來表示正負,所以JavaScript實際上可以表示-2e52到2e52之間的值。
2.2 string類型
string類型用來表示文本,可以使用單引號或者雙引號來包括文本,任何放在引號內的符號,都會被認為是string,但對於特殊符號,可能需要轉義處理。
2.3 boolean類型
boolean類型只包括兩個值:true和false。我們可以在程序中使用各種boolean表達式來得到true或者false,從而實現不同的業務分支處理。
我們可以在表達式中包含多個條件,條件之間可以是與或非的關系,在計算時,優先級如下:||的優先級最低,其次是&&,然后是比較運算符,最后是其他運算符(例如!)。
和其他許多語言一樣,對於&&來說,當前面的條件為false時,后面的條件不再計算,對於||來說,當前面的條件為true時,后面的條件不再計算。
來看下面的例子:
1 function conditionTest(){ 2 var a = 1; 3 var b = 1; 4 var c = {"key":"old"}; 5 print(c["key"]); 6 if (a==1) print("a = 1"); 7 if (a==1 && b==1) print("a == 1 && b == 1"); 8 if (a==1 || changeValue(c)) print(c["key"]); 9 if (a==1 && changeValue(c)) print(c["key"]); 10 } 11 12 function changeValue(obj){ 13 obj["key"] = "changed"; 14 return true; 15 }
它的輸出結果如下:
old a = 1 a == 1 && b == 1 old changed
可以看出,在使用||時,沒有調用changeValue方法。
2.4 undefined類型
當我們聲明了一個變量,但是沒有對其賦值時,它就是undefined的,就像下面這樣
1 var b; 2 print(b);
在Javascript中,還有一個和undefined類似的值:null。undefined表示“變量已聲明但是沒有復制”,null表示“變量已賦值但為空”,需要注意的是undefined==null的值為true。
2.5 類型轉換
我們在上面提到了undefined == null的值是true,但我們使用typeof操作時可以發現,null是object類型,這說明在比較的過程中,發生了類型轉換。
類型轉換是指將一種類型的值轉換成另外一種類型的值。我們使用==進行比較時,會有類型轉換,我們可以使用===來禁止類型轉換。
來看下面的例子:
1 function convertTypeTest(){ 2 var a = 1; 3 var b = "1"; 4 print ("a:" + a); 5 print ("b:" + b); 6 print ("type of a:" + typeof a); 7 print ("type of b:" + typeof b); 8 print ("a==b:" + (a == b)); 9 print ("a===b:" + (a === b)); 10 print ("a===Number(b):" + (a === Number(b))); 11 print ("String(a)===b:" + (String(a) === b)); 12 print ("type of undefined:" + typeof undefined); 13 print ("type of null:" + typeof null); 14 print ("undefined==null:" + (undefined == null)); 15 print ("undefined===null:" + (undefined === null)); 16 }
輸出結果如下:
a:1
b:1
type of a:number
type of b:string
a==b:true
a===b:false
a===Number(b):true
String(a)===b:true type of undefined:undefined type of null:object
undefined==null:true
undefined===null:false
可以很明顯看到==和===的區別。
3. JavaScript基本邏輯流程控制
和大部分編程語言一樣,JavaScript的邏輯控制基本上也是分為順序、循環和條件。
3.1 順序
上面給出的各個示例代碼都是順序執行的。
3.2 循環
JavaScript使用for、while來完成循環,下面是一個從1加到100的例子:
1 function loopTest(){ 2 var start = 1; 3 var end = 100; 4 var result = 0; 5 for (i = start; i <= end; i++){ 6 result = result + i; 7 } 8 print (result); 9 }
3.3 條件
JavaScript使用if、switch來完成條件判斷,下面是一個關於分數分類的例子:
1 function gradeLevel(grade){ 2 var level; 3 if (grade <= 100 && grade >= 90) level="優秀"; 4 else if (grade >= 80) level = "良好"; 5 else if (grade >= 60) level = "及格"; 6 else level="不及格"; 7 print(level); 8 }
4. 函數
函數是JavaScript很重要的一部分,它是模塊化的基礎。我們在上面的示例中已經定義了很多函數,接下來我們看一些更有意思的東西。
4.1 變量的作用域
在JavaScript中,變量的作用域可以分為全局作用域和局部作用域。在函數體內部聲明的變量屬於局部變量。
來看下面的例子:
1 var a = 1; 2 3 function change1(){ 4 a=2; 5 } 6 7 function change2(){ 8 var a =3; 9 } 10 11 function print(obj){ 12 document.write(obj); 13 document.write("<br>"); 14 } 15 16 17 //調用順序 18 print(a); 19 change1(); 20 print(a); 21 change2(); 22 print(a);
從運行結果可以看出,change1修改了全局變量a的值,但change2沒有修改,這是因為在change2中定義了一個同名的局部變量,所以在該方法體內發生的操作只作用在局部變量上。
4.2 嵌套函數
JavaScript允許在函數體內嵌套函數,這樣從某種程度上,可以控制一些變量在某些函數內共享。
下面是一個實現四則運算的例子:
1 function operation(left,right,op){ 2 function add(){ 3 return left + right; 4 } 5 function minus(){ 6 return left - right; 7 } 8 function multiple(){ 9 return left*right; 10 } 11 function divide(){ 12 return left/right; 13 } 14 if (op=="+") print (add()); 15 else if (op=="-") print (minus()); 16 else if (op=="*") print (multiple()); 17 else if (op=="/") print (divide()); 18 else print ("Invalid operation."); 19 }
對於示例代碼,我們可以發現,內嵌函數可以訪問外部函數的參數、全局變量等。
在調用函數時,通常是沒有辦法直接訪問內嵌函數的。
4.3 函數調用的機制
JavaScript使用棧和上下文的機制來調用函數,我們可以將JavaScript執行時的每一行代碼的順序看做是上下文,並且使用棧來存儲上下文。
當我們調用一個方法時,在調用前,棧會保存當前上下文,然后調用函數,在調用結束后,棧會從之前保存過的上下文開始繼續執行。
如果上下文增長過快,會導致堆棧溢出,例如當我們在A方法中調用B方法,然后在B方法中調用A方法,這種循環調用的方式很容易就將堆棧搞壞了。
4.4 函數作為值的處理
在JavaScript中,函數也是一種值,這是和其他語言不太一樣的。
來看下面的代碼:
1 var a=false; 2 function test(){ 3 print("test"); 4 } 5 6 (a||test)(); 7 8 var b=test; 9 b();
如果要理解“函數都是值”的意思,我們可以從某種程度上認為b是一個函數指針,它指向了test函數。
4.5 閉包
JavaScript中的閉包是指函數的返回值是一個函數,並且返回值函數使用了外部函數的變量(參數或者局部變量)。
對於嵌套函數來講,它可以訪問外部函數的參數或者局部變量,示例如下:
1 function foo(x){ 2 var y=1; 3 function bar(z){ 4 y=y+1; 5 print(x+y+z); 6 } 7 bar(1); 8 } 9 10 foo(1);
在這里,嵌套函數bar訪問了外部函數foo的參數x及局部變量y,並且對y有加1的操作,但這不叫閉包,我們多次調用foo(1),並沒有使得y遞增。
下面才是閉包:
1 function foo(x){ 2 var y =1; 3 return function(z){ 4 y = y+1; 5 print(x+y+z); 6 } 7 } 8 9 var bar=foo(1); 10 bar(1);
我們可以看到上面代碼中foo的返回值本身就是一個匿名函數,如果我們多次調用bar(1),可以發現foo函數的局部變量y的值是一直在遞增。
閉包在實際的應用中,一般是用來封裝對復雜數據結構的各種操作,從而對外提供簡單易用的接口,用戶在使用時不用太關心閉包內部的實現細節。
4.6 函數的可變參數
JavaScript在調用函數時,並不會限制傳入參數的個數,當傳入參數的個數多於函數定義中參數的個數時,多余的參數會被忽略;當傳入參數的個數小於函數定義中參數的個數時,缺失的參數默認是undefined。
我們來看下面的例子:
1 function argumentTest(v1,v2,v3,v4){ 2 print(v1+v2+v3+v4); 3 } 4 5 //調用方法 6 argumentTest(1,2,3); 7 argumentTest(1,2,3,4,5); 8 9 10 //輸出結果 11 NaN 12 10
可以看到,當我們給出的參數有缺失時,默認的undefined和其他參數做運算時,返回了NaN。當我們給出的參數有富余時,被自動忽略了。
在JavaScript中,函數中有默認的一個變量,名為arguments(在這里,arguments並不是一個數組,typeof arguments返回的是object),它用來保存所有傳入的參數,我們來看下面的例子:
1 function argumentTest(v1,v2,v3,v4){ 2 var result=0; 3 for(var i = 0; i < arguments.length;i++){ 4 result=result+arguments[i]; 5 } 6 print(result); 7 } 8 9 //調用方法 10 argumentTest(1,2,3); 11 argumentTest(1,2,3,4,5); 12 13 //執行結果 14 6 15 15
這里我們可以看到,無論我們傳入了多少參數,都可以被適當的處理了。