1、JS流程控制語句
(1)、if 判斷
if 語句是基於條件成立時才執行相應的代碼。
if...else 語句是在指定的條件成立時執行if後的代碼,在條件不成立時執行else後的代碼。
if...else 嵌套語句是在多種條件下選擇相應的代碼快之一來執行。
if 語句適用於任意類型的數據,可處理復雜的邏輯關系。
(2)、switch語句
當有很多種選擇的時候,switch 比 if...else 使用更方便,結構簡潔,專為多重選擇設計,但是僅能處理多個枚舉型邏輯關系。該語句使用 if 也可以完成,這個看個人喜好。
switch 語句的工作原理:首先創建一個表達式,通常為變量,之後表達式的值與 switch 語句中每個 case 的值做比較,如果匹配,則執行該 case 後的語句,若與所有 case 值都不匹配,則執行 default 後的語句。在使用 switch 語句時,每個 case 語句後必須使用 break 跳出循環,阻止運行下一個 case。
var d = new Date().getDay(); //如果今天不是周末,則提示默認消息 switch (d){ case 6: alert("今天是星期六"); break; case 0: alert("今天是星期天"); break; default: alert("同志尚未努力,革命仍需成功。"); }
switch 語句在做比較時,使用的是全等,而不是相等,所以在字符串與數字匹配時,需要特別注意。
//使用switch語句將字符串與數字做比較 //返回:不等於,執行default語句 var n = '5'; switch(n){ case 5: alert('等於,執行case語句'); break; default: alert('不等於,執行default語句'); } //使用if語句將字符串與數字做比較 //返回:等於 var n = '2'; if(n == 2){ alert('等於'); }else{ alert('不等於'); } //將case的值改為字符串再做比較 //返回:等於,執行case語句 var n = '2'; switch(n){ case '2': alert('等於,執行case語句'); break; default: alert('不等於,執行default語句'); } //使用全等再做比較 //返回:不等於 var n = '2'; if(n===2){ alert('等於'); }else{ alert('不等於'); }
(3)、for 循環
很多事情不只是做一次,需要重復做。比如打印 10 份文件,每次打印 1 份,重復這個動作,直到打印完成。這樣的事情就用 for 循環來完成,循環就是重復執行一段代碼,每次的值不同。
下面是一個 for 循環的小應用,假設有 1.2.3. ... 10 不同面值的 RMB,計算一共有多少 RMB。
var sum = 0; for(var rmb=1; rmb<=10; rmb++){ sum += rmb; } alert('一共有: ' + sum + '元'); //返回:一共有:55元
(4)、while 循環
while 循環和 for 循環具有相同的功能,只要指定條件為 ture,循環就可以一直執行,直到條件不再滿足。
//使用while循環輸出5個數字 var i = 0; //第一部分:初始化,給一個初始的值,讓i從0循環 while(i < 5){ //第二部分:條件。成立繼續循環,不成立退出 alert(i); //第三部分:語句 i++; //第四部分:自增 } //while循環使用比較復雜,使用for循環更簡潔。 //for(初始化;條件;自增){語句}
(5)、do...while 循環
do...while 循環與 while 循環的原理結構是基本相同的,但是該循環會在檢查條件是否為 ture 之前執行一次代碼塊,如果條件為 ture,則重復循環。該循環有一點小問題,因為他是先執行代碼,後判斷條件,如果條件不當,則進入死循環,導致浏覽器崩潰。
/* 語法: do{ 執行語句 } while(條件); */ //操作有風險,嘗試需謹慎 //若將循環條件改為:num <= 6 會導致浏覽器崩潰 var num = 6; do{ document.write("數字:" + num + "<br>"); num -= 1; } while(num > 0);
(6)、JS 錯誤處理語句
try...catch 語句用於進行異常處理。try 語句用於檢測代碼塊的錯誤,指明需要處理的代碼段,catch 語句用於處理 try 語句中拋出的錯誤。try 語句首先被執行,如果運行中發生了錯誤,try 語句中的代碼將被跳過執行 catch 中的語句。如果沒有發生錯誤,則不執行 catch 中的語句。一般針對可預見性的錯誤,可使用 try...catch 語句進行處理。
try{ document.write("開始執行try語句" + '<br>'); document.write("還沒拋出錯誤" + '<br>'); alert(x); //拋出錯誤 alert('123'); //沒被執行 } catch(e){ document.write("捕捉到錯誤,開始執行catch語句" + '<br>'); document.write("錯誤類型: " + e.name + '<br>'); document.write("錯誤信息: " + e.message); alert('x'); }
throw 語句可用於創建自定義錯誤。官方術語為:創建或拋出異常(exception)。語法:throw '異常對象'。
throw 語句可以配合 try...catch 語句一起使用,以達到控制程序流,生成精確的錯誤消息。
//輸入0到10之間的數字,如果輸入錯誤,會拋出一個錯誤,catch會捕捉到錯誤,並顯示自定義的錯誤消息。 <body> <input id="txt" type="text"/> <span id="demo" style="font-weight:bold;"></span><br> <input type="button" value="檢測輸入" onclick="error()"> <script> function error(){ try{ var x = document.getElementById("txt").value; var y = document.getElementById("demo"); y.style.color = 'red'; if(x == '') throw '輸入不能為空'; if(isNaN(x)) throw '請輸入數字'; var num = [7,8,9]; for(var i=0; i<num.length; i++){ if(x == num[i]){ throw '該數字已經存在'; } } if(x == 0){ throw '輸入不能為0'; } else if(x > 10){ throw '數字太大了'; } else if(x <= 3){ throw '數字太小了'; } else{ y.style.color = 'green'; y.innerHTML = 'OK'; } } catch(e){ y.innerHTML = '錯誤提示:' + e + '!'; } } </script> </body>
(7)、跳出循環
break 語句用於跳出當前循環,直接退出循環執行後面的代碼,即終止整個循環,不再進行判斷。continue 語句僅僅是跳出本次循環,繼續執行後面的循環,即結束本次循環,接著去判斷是否執行下次循環。return 可以終止函數體的運行,並返回一個值。
for(var i=0; i<6; i++){ if(i == 3) break; //當i=3時跳出整個循環,不再執行循環 alert(i); //返回:0,1,2 } for(var i=0; i<6; i++){ if(i == 3) continue; //當i=3時跳出本次循環,繼續執行後面循環 alert(i); 返回:0,1,2,4,5 }
2、JSON
JSON(JavaScript Object Notation):JS 對象表示法。JSON 主要用於存儲和交換數據信息,類似於 XML,但是相比 XML,JSON 易於閱讀和編寫,也易於解析。
JSON 語法是 JS 對象表示語法的子集:數據在鍵值對中,並由逗號分隔,花括號保存對象,方括號保存數組。
JSON 語法的書寫格式:"名稱" : "值", "名稱" : "值"
名稱和值包含在雙引號中,並用冒號分隔,每條數據用逗號分隔。這很容易理解,相對於 JS 中 名稱 = "值"。
JSON 的值可以是:數字(包括整數和小數),字符串(包含在雙引號中),布爾值(true 或 false),對象(包含在花括號中),數組(包含在方括號中),或者為 null。
JSON 是純文本,通常用於服務端向網頁傳遞數據,從服務器上獲取 JSON 數據,然後在網頁中使用該數據。
(1)、JSON對象
var json = {"a": 12, "b": "abc", "c":[1,2,3]}; //返回第一項的值: alert(json.a); //修改第二項的值 alert(json.b = "xyz"); //返回第三項數組中第一項的值 alert(json.c[0]);
(2)、JSON 和數組
相同點:
都可以通過下標返回某項的值。都可以使用循環。雖然 JSON 沒有 length 屬性,不能使用 for 循環,但是可以使用 for...in 循環,完成與 for 循環相同的動作。
數組也可以使用 for...in 循環,但最好還是使用 for 循環。for...in 循環遍歷的是對象的屬性,而不是數組元素。
不同點:
JSON 的下標是字符串,數組的下標為數字。JSON 沒有 length 屬性,數組有該屬性。
var arr = [12,5,7]; var json = {"a":12,"b":5,"c":7}; alert(arr[0]); //返回:12 alert(json["a"]); //返回:12 alert(arr.length); //返回:3 alert(json.length); //返回:undefined //數組for循環 for(var i=0; i<arr.length; i++){ alert('第' + (i+1) + '個數據是:' + arr[i]); } alert(typeof i); //返回:number //數組使用for...in循環 for(var i in arr){ alert('第' + (i+1) + '個數據是:' + arr[i]); } alert(typeof i); //返回:string //JSON使用for...in循環 for(var i in json){ alert('第' + i + '個數據是:' + json[i]); }
(3)、JSON 數組對象
<body> <p> 姓 名: <span id="fname"></span><br> 性 別: <span id="gender"></span><br> 員工號: <span id="num"></span><br> 修改姓名: <span id="lname"></span><br> </p> <script> var staff = [ {"name" : "小明", "sex" : "男", "id" : 1}, {"name" : "小白", "sex" : "男", "id" : 2}, {"name" : "小紅", "sex" : "女", "id" : 3} ]; var x = document.getElementById("fname"); var y = document.getElementById("gender"); var z = document.getElementById("num"); var n = document.getElementById("lname"); //訪問對象數組中第一項的值: x.innerHTML = staff[0].name; y.innerHTML = staff[0].sex; z.innerHTML = staff[0].id; //修改數據: n.innerHTML = staff[1].name = '大白'; </script> </body>
(4)、JSON 字符串對象
var str = '{"name":"小明", "sex":"男", "age":21}'; var toObj = JSON.parse(str); //JSON字符串轉換為JSON對象 alert(toObj.name); alert(typeof toObj); //返回:object var json = {"name":"小紅", "sex":"女", "age":18}; var toStr = JSON.stringify(json); //JSON對象轉換為JSON字符串 alert(toStr); //返回字符串 alert(json.age); alert(typeof toStr); //返回:string
(5)、JSON 應用
當需要表示一組數據時,JSON 不但能夠提高可讀性,而且還可以減少復雜性。JSON 能夠表示多個值,每個值又可包含多個值,例如要表示一個用戶列表信息,就可以將所有信息存儲在一個變量中,分成多項,每項中又可分成多個條目,每個條目中記錄一個用戶的信息。
var userName = { "first": [{ "name": "路飛", "sex": "男", "tel": "aaa" }, { "name": "索羅", "sex": "男", "tel": "bbb" }, { "name": "娜美", "sex": "女", "tel": "ccc" }], "second": [{ "name": "卡卡西", "sex": "男", "tel": "ddd" }, { "name": "鳴人", "sex": "男", "tel": "fff" }, { "name": "佐助", "sex": "男", "tel": "eee" }, { "name": "皺田", "sex": "女", "tel": "sss" }], "third": [{ "name": "小明", "sex": "男", "tel": "xxx" },{ "name": "小紅", "sex": "女", "tel": "zzz" }] }; //獲取用戶的信息: alert(userName.first[1].name + ' \n ' + userName.first[1].sex + '\n '+ userName.first[1].tel); alert(userName.second[3].name + ' \n ' + userName.second[3].sex +' \n '+ userName.second[3].tel); alert(userName.third[0].name + ' \n ' + userName.third[0].sex + ' \n '+ userName.third[0].tel);
說到 JSON,就不得不提一下 JSONP。JSONP (JSON with Padding) 是 JSON 的一種 "使用模式",可以讓網頁從別的域名(網站)那獲取資料,即跨域讀取數據。可用於解決主流浏覽器的跨域數據訪問的問題。為什麼我們從不同的域(網站)訪問數據需要一個特殊的技術 (JSONP) 呢?這是因為同源策略。同源策略,它是由 Netscape 提出的一個著名的安全策略,現在所有支持 JavaScript 的浏覽器都會使用這個策略。由於該策略,一般來說位於 server1 的 demo.com 的網頁無法與不是 server1 的 demo.com 的網頁的服務器溝通,而 HTML 的 <script> 元素是一個例外。利用 <script> 元素的這個開放策略,網頁可以得到從其他來源動態產生的 JSON 資料,而這種使用模式就是所謂的 JSONP。用 JSONP 抓到的資料並不是 JSON,而是任意的 JavaScript,用 JavaScript 直譯器執行而不是用 JSON 解析器解析。
3、JS 定時器
定時器可以在指定的時間間隔之後再執行代碼,而不是在函數被調用後立即執行。定時器在網頁中應用非常廣泛,最常見的就是動態時鐘,還有比如購物網站的倒計時搶購。定時器的類型可分為兩類:一類是間隔型,即 setInterval,在執行時,從頁面加載後每隔一段時間執行一次,可無限執行。另一類是延遲型,即 setTimeout,在頁面加載後延遲指定的時間,去執行一次,而且僅僅只執行一次。該方法屬於 window 對象的兩個方法。
(1)、setInterval
setInterval(function, time) 方法可間隔指定的毫秒數,不停的執行指定的代碼。該方法有兩個參數,第一個參數是函數,指定定時器要調用的函數或要執行的代碼串,第二個參數是時間,用毫秒計,1000 毫秒是 1 秒,指定執行的間隔時間。
(2)、setTimeout
setTimeout(function, time) 方法可延遲指定的毫秒數後,再執行一次指定的代碼。該方法也有兩個參數,第一個參數為函數,指定要調用的函數或代碼串,第二個參數指定在執行代碼前需要等待多少毫秒。
function show(){ alert(1); } //當頁面加載後,每隔1秒彈出一個1,無限次執行 setInterval(show,1000); //當頁面加載後,在1秒後彈出一個1,只執行一次 setTimeout(show,1000);
setInterval 動態時鐘效果:
//動態顯示時鐘 <p id="demo"></p> <script> function clock(){ var d = new Date(); var time = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds() ; var oP = document.getElementById("demo").innerHTML = time; } setInterval(clock,1000); </script>
setTimeout 統計效果:
//setTimeout可實現統計: <input type="text" id="demo" > <script> var num = 0; function start() { document.getElementById('demo').value = num; num += 1; setTimeout(start,1000); } setTimeout(start,1000); </script>
可以開啟定時器,也就可以關閉定時器。兩種類型對應著兩種方法。
(1)、clearInterval
clearInterval() 方法可關閉由 setInterval() 方法執行的函數代碼。使用該方法關閉定時器時,在創建間隔定時器時必須使用全局變量。
開始、停止動態時鐘效果:
//開始、停止動態時鐘 <input type="text" id="txt1" > <input type="button" value="停止" onclick="stop()" > <input type="button" value="開始" onclick="start()" > <script> var time = null; function start(){ time = setInterval(function (){ var d = new Date(); var t = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds(); var oTxT = document.getElementById("txt1").value = t; },1000); }; start(); function stop(){ clearInterval(time); } </script>
(2)、clearTimeout
clearTimeout() 方法用於停止執行由 setTimeout() 方法執行的函數代碼。使用該方法時關閉定時器時,在創建延遲定時器時必須使用全局變量。
開始、停止統計效果:
//開始、停止統計: <input type="text" id="txt1" > <input type="button" value="停止" onclick="stop()" > <input type="button" value="開始" onclick="start()" > <script> var num = 0; var time = null; function start(){ var oTxt = document.getElementById('txt1').value = num; num += 1; time = setTimeout('start()',1000); } start(); function stop(){ clearTimeout(time); } </script>
4、Event 對象
Event 對象代表事件的狀態,用於獲取事件的詳細信息,如鼠標按鈕、鼠標位置、鍵盤按鍵。事件通常與函數一起使用,函數不會在事件發生前被執行。
(1)、獲取鼠標坐標
screenX 和 screenY 返回鼠標相對於屏幕的水平坐標和垂直坐標。參照點為屏幕的左上角。
clientX 和 clientY 返回鼠標相對於當前窗口可視區的水平坐標和垂直坐標。參照點為浏覽器頁面的左上角。
document.onclick = function (){ //可視區坐標 alert(event.clientX + ',' + event.clientY); //屏幕坐標 alert(event.screenX + ',' + event.screenY); }
(2)、獲取鼠標鈕按
button 事件屬性用於獲取鼠標哪個按鈕被點擊了。返回一個整數,0 代表左鍵,1 代表中鍵,2 代表右鍵。
//鼠標左右按鍵 document.onmousedown = function (){ alert(event.button); }
(3)、獲取鍵盤按鍵
keyCode 事件屬性用於獲取按下了鍵盤的哪個鍵,返回鍵碼,表示鍵盤上真實鍵的數字。
//鍵盤按鍵 document.onkeydown=function (){ alert(event.keyCode); };
鍵盤按鍵的 ctrlKey、shiftKey 和 altKey 快捷屬性,可判斷是否按下了該鍵,返回一個布爾值,指示在事件發生時,改鍵是否被按下。1 表示被按下,0 表示沒有按下。
document.onkeydown = function (){ if(event.ctrlKey == 1){ alert('Ctrl鍵被按了'); } else if(event.shiftKey == 1){ alert('Shift鍵被按了'); } else if(event.altKey == true){ alert('Alt鍵被按了'); } else{ alert('都沒被按下'); } };
實例:按鍵提交消息
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>JavaScript示例</title> <script> window.onload=function (){ var oTxt1 = document.getElementById('txt1'); var oTxt2 = document.getElementById('txt2'); var oBtn = document.getElementById('btn1'); //點擊按鈕提交 oBtn.onclick = function (){ //輸入框的值等於文本框的值。 oTxt2.value += oTxt1.value + '\n'; //清空輸入框的值,以便再次輸入 oTxt1.value = ''; }; //按Enter或Ctrl+Enter提交 oTxt1.onkeydown = function (){ //回車鍵的鍵碼為13 if(event.keyCode == 13 || event.keyCode == 13 && event.ctrlKey){ oTxt2.value += oTxt1.value + '\n'; oTxt1.value = ''; } }; } </script> </head> <body> <input id="txt1" type="text" > <input id="btn1" type="button" value="提交"><br> <textarea id="txt2" rows="10" cols="50" ></textarea> </body> </html>
(4)、事件流
事件的傳遞有兩種方式:冒泡與捕獲。事件傳遞定義了元素觸發事件的順序。
事件冒泡:當一個元素發生事件後,事件會順著層級(父級 - 父級的父級 --)關系依次向上傳遞直到 document。
事件捕獲:事件捕獲與事件冒泡正好相反,外部元素的事件會先被觸發,然後才會觸發內部元素的事件,即從祖先到後代。
事件流同時支持兩種事件方式,冒泡型事件和捕獲型事件,但是捕獲型事件先發生。
兩種事件流會觸發 DOM 中的所有對象,從 document 對象開始,也在 document 對象結束。
語法:addEventListener('事件名稱',函數,冒泡/捕獲)
addEventListener() 方法用於向指定元素添加事件,該方法不會覆蓋已存在的事件,可同時向一個元素添加多個事件。該方法有三個參數,第一個參數定義事件的類型,
第二個參數規定事件觸發後調用的函數,第三個參數是布爾值,用於定義該事件是冒泡還是捕獲,若為 false,則表示冒泡事件,若是 ture,則表示捕獲事件。
這裡需要注意是的該方法的事件類型,不需要加”on“,比如平時寫點擊事件:“onclick”,該方法中則使用“click”即可。
<!DOCTYPE html> <html id="htm"> <head> <meta charset="utf-8" /> <title>JavaScript示例</title> <style> div{padding:50px;} </style> <script> window.onload = function (){ var x = document.getElementById("div1"); var y = document.getElementById("div2"); var z = document.getElementById("div3"); var o = document.getElementById("bod"); var n = document.getElementById("htm"); x.addEventListener("click", function() { alert("1冒泡"); }, false); y.addEventListener("click", function() { alert("2冒泡"); }, false); z.addEventListener("click", function() { alert("3冒泡"); }, false); o.addEventListener("click", function() { alert("body捕獲"); }, true); n.addEventListener("click", function() { alert("html冒泡"); }, false); }; </script> </head> <body id="bod"> <div style="background:lightgreen;margin-bottom:10px;">我是body元素,我捕獲。祖先html也會冒泡。</div> <div id="div3" style="background:#ccc;">我是div3,我冒泡 <div id="div2" style="background:green;">我是div2,我冒泡 <div id="div1" style="background:red;">我是div1,我冒泡</div> </div> </div> </body> </html>
removeEventListener() 方法用於移除由 addEventListener() 方法添加的事件監聽。這裡需要注意在綁定函數時不能使用匿名函數,否則無法刪除。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JavaScript示例</title> <style> #div1{ background:green; padding:50px; color:white; } </style> </head> <body> <div id="div1"> div元素添加了onmousemove事件監聽,鼠標在綠色區域內移動時會顯示隨機數。 <p>點擊按鈕可移除div的事件監聽。</p> <button id="btn1">點擊移除</button> </div> <p id="p1"></p> <script> var oDiv = document.getElementById("div1"); var oP = document.getElementById("p1"); var oBtn = document.getElementById("btn1"); oDiv.addEventListener("mousemove", block, false); function block(){ oP.innerHTML = Math.random()*10; } oBtn.onclick = function (){ oDiv.removeEventListener("mousemove", block, false); }; </script> </body> </html>
cancelBubble 方法可取消事件冒泡,不會往父級傳遞。實例:仿百度翻譯效果,點擊顯示按鈕顯示 div,隨便點擊頁面其他位置隱藏 div。
如果不取消事件冒泡,則在點擊按鈕的同時 div 先是顯示了,然後又立馬被隱藏了,可以注釋掉取消事件冒泡代碼,用彈窗查看效果。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JavaScript示例</title> <style> #div1{ width:500px; height:300px; background:lightgreen; display:none; } </style> <script> window.onload = function (){ var oBtn = document.getElementById("btn1"); var oDiv = document.getElementById("div1"); //點擊按鈕顯示div oBtn.onclick = function (){ oDiv.style.display = 'block'; //alert('顯示'); //取消事件冒泡,不會往父級傳遞。 event.cancelBubble = true; }; //點擊document隱藏div document.onclick = function (){ oDiv.style.display = 'none'; }; }; </script> </head> <body> <input id="btn1" type="button" value="顯示"> <div id="div1"></div> </body> </html>
(5)、默認事件
所謂的默認事件,就是浏覽器自帶的事件。比如按下鍵盤按鍵,浏覽器會自動將按鍵值寫入輸入框。再比如新建一個空白頁面在浏覽器打開,點擊右鍵出現菜單項。我們並沒有用 JS 寫相關判斷,如果點擊右鍵觸發什麼事件。這就是浏覽器的默認事件,也叫默認行為。如果我們想彈出自定義的右鍵菜單項,這時候就需要阻止掉浏覽器的默認行為,阻止默認事件最簡單的寫法就是 return false; 。
實例:只能輸入數字鍵的輸入框,不考慮小鍵盤區。
實現思路:鍵盤區數字鍵 0 的鍵碼是 48,1 是 49,9 是 57,那麼就可以做出判斷,如果按鍵小於 48 或大於 57,則阻止掉,這說明按下的不是數字鍵,考慮到寫錯了需要刪除,或者少寫了,需要光標移動到少寫的位置補上,再移回繼續輸入,那麼就再加上判斷條件,如果按鍵不是退格鍵或者不是左右方向鍵,則阻止掉。刪除鍵(退格鍵)的鍵碼是 8,左方向鍵是 37,右方向鍵為 39。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JavaScript示例</title> <script> window.onload = function (){ var oTxt = document.getElementById('txt1'); oTxt.onkeydown = function (){ //如果按下的不是刪除鍵8,並且不是左方向鍵37,並且不是右方向鍵39,並且按鍵鍵碼小於48或大於57,則阻止掉 if(event.keyCode !=8 && event.keyCode != 37 && event.keyCode != 39 && event.keyCode < 48 || event.keyCode > 57){ return false; } }; }; </script> </head> <body> <input id="txt1" type="text" placeholder="請輸入數字"> </body> </html>
5、JS 知識點
(1)、JS 引擎的預解析機制
JS 引擎的解析過程可分為兩個階段:預解析階段和執行階段。
JS 預解析是在程序進入一個新環境時,把該環境中的變量和函數預解析到他們能調用的環境中,即每一次的預解析單位是一個執行環境。當文檔流中有多個 JS 代碼段,每個代碼段用 script 標簽分隔,包括外部引入的 JS 文件,JS 引擎並非是逐行的解析程序,而是分段進行的,即一個執行環境。預解析不能跨代碼段執行,簡單說就是不能在一個代碼段聲明,在另一個代碼段調用。
變量和函數的預解析就是提升,所謂提升(hoisting),就是 JS 引擎在執行時,默認將所有的變量聲明和函數聲明都提升到當前作用域的最前邊去的行為。所以函數可以在聲明之前調用。需要注意的是在使用表達式定義函數時無法提升。
<script> alert(a(2)); //返回:4 function a(x){ return x * x; } alert(b); //返回:undefined var b = 'hello'; alert(c(2)); //報錯 //實際上是以變量聲明提升 //相當於:c(); var c = undefined; c = function (){} var c = function (y){ return y * y; } function d(){ var n = 'hello'; } alert(n); //報錯 </script>
通過上面的代碼可以看出,function 定義的函數聲明 (a) 在代碼開始執行之前(預解析階段)對其實現了函數聲明提升,先將其放入內存中,所以在函數聲明之前可以調用該函數。和函數聲明一樣,變量聲明 (b) 也會在一開始被放入內存中,但是並沒有賦值,所以在他賦值之前,他的值就是 undefined。但是函數表達式 (c) 不同,函數表達式用 var 聲明,也就是說解析器會對其變量提升,並對其賦值為 undefined,然後在執行期間,等到執行到該 var 變量的時候再將其變量指向一個 function 函數,所以在函數表達式之前執行該函數就會報錯。函數 (d) 是在函數內聲明的變量,那麼這個變量是屬於該函數的私有變量,所以在外部調用時會報錯。
下面實例實例說明了每一次 JS 預解析的單位是一個執行環境,不會跨一個代碼段去執行,直接會報錯。
<script> alert(a);//報錯:a is not defined </script> <script> var a = 'hello'; </script>
若定義了兩個同名的函數 (b),則在預解析時後面的一個會覆蓋掉前邊的一個。若變量 (a) 和函數重名 (a),則函數的優先級高於變量的優先級。
<script> alert(a); //返回:function a(){alert('hi');} var a = 'hello'; function a(){ alert('hi'); } alert(a); //返回:hello b(); //返回:2 function b(){ alert(1); } b(); //返回:2 function b(){ alert(2); } </script>
(2)、回調函數
簡單理解,所謂回調,就是回頭調用,那麼回調函數就是一個函數調用的過程。比如函數 a 有一個參數,這個參數是一個函數 b,當函數 a 執行完以後再執行函數 b,那麼這就是一個回調的過程。用官方術語解釋就是:回調是一個函數作為參數傳遞給另一個函數,其母函數完成後執行。那麼函數 a 就是母函數。這裡需要注意:函數 b 是以參數的形式傳遞給函數 a 的,那麼這個函數 b 就被稱為回調函數。回調函數不是由母函數直接調用的,而是在特定的事件或者條件發生時由另外的一方調用,用於對該事件進行響應。回調函數必須使用關鍵字 callback,並且回調函數本身必須是全局函數。
JS 回調的原理是一個異步的流程,在異步調用的情況下使用性能很好,舉個簡單的例子更能具體的說明,比如朋友要來找你,等到門口了給你打電話。"來找你" 就是函數 a 開始執行,而這時候"你"可以去做任何事情,"到門口"就是函數 a 執行完畢,"給你打電話"這就屬於回調函數 b,然後你們就可以一起愉快的玩耍了。
下面是一個簡單的回調函數實例,點擊按鈕,當函數 a 執行完成後,分別調用回調函數 b、c、d。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JavaScript示例</title> <script> function a(callback){ alert("我是母函數a"); alert("現在要開始調用回調函數"); callback(); } function b(){ alert("我是回調函數b"); } function c(){ alert("我是回調函數c"); } function d(){ alert("我是回調函數d"); } function back(){ a(b); a(c); a(d); } </script> </head> <body> <button onclick="back()">點擊按鈕</button> </body> </html>
(3)、自調用函數
自調用函數也叫立即執行函數。使用關鍵字 function 定義一個函數,在給這個函數指定一個函數名,這叫函數聲明。使用關鍵字 function 定義一個函數,未給函數命名,在將這個匿名函數賦值個一個變量,這叫做函數表達式,這也是最常見的函數表達式語法。匿名函數就是使用關鍵字 function 定義一個函數,但是不給函數命名,匿名函數屬於函數表達式。匿名函數有很多作用,可賦予變量創建函數,賦予一個事件則稱為事件處理程序。
函數聲明和函數表達式的區別:JS 引擎在解析 JS 代碼時當前執行環境中的函數聲明會提升,而函數表達式只能等到 JS 引擎執行到他所在的作用域時,才會逐行的解析函數表達式。函數聲明不能使用自調用,而函數表達式可以使用自調用。
函數的自調用就是在函數體後加括號,再用括號整個包起來。這樣說明該函數是一個函數表達式。
(function (a, b) { alert(a * b); }(2, 3)); //返回:6
(4)、構造函數
函數通過關鍵字 function 定義,也可以使用關鍵字 new 定義。函數即對象,對象具有屬性和方法,構造函數就是將定義的函數作為某個對象的屬性,函數定義作為對象的屬性,則稱之為對象方法。函數如果用於創建新的對象,就叫做對象的構造函數。
(5)、閉包
簡單理解就是:子函數可以使用父函數中的局部變量。之前好幾個例子中都用到了閉包,就是 window.onload 函數下定義的點擊事件函數。
JS 的變量可以是全局變量或局部變量,在函數之外聲明的變量就是全局變量,函數之內聲明的變量就是局部變量,私有變量可以用到閉包。函數可以訪問全局的變量,也可以訪問局部的變量。作用域就是變量和函數的可訪問范圍,即作用域控制著變量和函數的可見性與生命周期,忽略塊級作用域,作用域可分為全局作用域和局部作用域。全局變量是全局對象的屬性,局部變量是調用對象的屬性。全局變量屬於 window 對象,全局變量在任何地方都可以訪問,局部變量只能用於定義他的函數內部,這就是 JS 的作用域鏈,即內層函數可以訪問外層函數的局部變量,外層函數不能訪問內層函數的局部變量。全局變量和局部變量即便名稱相同,他們也是兩個不同的變量。修改其中一個,不會修改另一個的值。這裡需要注意:在函數內聲明的量,如果不使用關鍵字 var ,那麼他就是一個全局變量。
所有函數都能訪問全局變量,也就是所有的函數都可以訪問他上一層的作用域。JS 支持函數嵌套,嵌套函數可以訪問上一層的函數變量。閉包就是可以訪問上一層函數作用域中的變量函數,即便上一層函數已經關閉。
閉包實例解析:
如果想實現點擊按鈕計數,可以聲明一個變量,並賦初始值為 0,函數設置值加 1。但是這個全局變量在任何地方都可以使用,即便沒有調用這個函數,計數也會改變。
<body> <input type="button" value="全局變量計數" onclick="show()"> <input type="button" value="調用一次變量" onclick="change()"> <p id="p1">0</p> <script> var num = 0; function count() { return num += 1; } var oP = document.getElementById("p1"); function show(){ oP.innerHTML = count(); } function change(){ alert(num = 10); } </script> </body>
上面例子,每次點擊按鈕計數正常,但如果調用一次變量,給變量賦值為 10,再點按鈕將從 11 開始計數。那麼可以將這個變量聲明在函數內,如果沒有調用這個函數,計數將不會改變。
<body> <input type="button" value="點擊計數" onclick="show()"> <p id="p1">0</p> <script> function count() { var num = 0; return num += 1; } var oP = document.getElementById("p1"); function show(){ oP.innerHTML = count(); } </script> </body>
點擊按鈕可以看到事與願違,雖然這樣不能在函數外部使用變量,也就不能修改計數,但是每次點擊按鈕值都為 1。因為變量是在函數內聲明的,只有該函數可以使用,每點擊按鈕一次,調用一次該函數,每次調用變量的初始值都為 0,再加 1 就是 1。那麼使用 JS 的嵌套函數可以完成這一問題,內嵌函數可以訪問父函數的變量。
<body> <input type="button" value="計數" onclick="show()"> <p id="p1">0</p> <script> function count(){ var num = 0; function add(){ num += 1; } add(); return num; } add(); //報錯:未定義 function show(){ document.getElementById("p1").innerHTML = count(); } </script> </body>
雖然這樣可以解決變量的問題,但是如果可以在外部調用 add() 函數的話,那麼點擊按鈕計數就完美了,夢想總是美好的,現實卻是殘酷的,內嵌函數不能在外部被調用。這時候我們的閉包就來了,我們需要閉包,有了閉包這個問題就真的完美了。
<body> <input type="button" value="計數" onclick="show()"> <p id="p1">0</p> <script> var count = (function (){ var num = 0; return function (){ return num += 1; }; }()); var oP = document.getElementById('p1'); function show(){ oP.innerHTML = count(); } </script> </body>
變量 count 指定了函數自我調用返回值,自我調用函數只執行一次,計數初始值為 0,並返回函數表達式,計數受匿名函數作用域的保護,只能通過 count() 方法修改。
變量 count 可以作為一個函數使用,他可以訪問函數上一層作用域的計數,這就叫做 JS 閉包,函數擁有自己的私有變量。