這篇文章裡,我們來聊一些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
這裡我們可以看到,無論我們傳入了多少引數,都可以被適當的處理了。