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 语句一起使用,以达到控制程序流,生成精确的错误消息。
1 //输入0到10之间的数字,如果输入错误,会抛出一个错误,catch会捕捉到错误,并显示自定义的错误消息。 2 <body> 3 <input id="txt" type="text"/> 4 <span id="demo" style="font-weight:bold;"></span><br> 5 <input type="button" value="检测输入" onclick="error()"> 6 <script> 7 function error(){ 8 try{ 9 var x = document.getElementById("txt").value; 10 var y = document.getElementById("demo"); 11 y.style.color = 'red'; 12 if(x == '') throw '输入不能为空'; 13 if(isNaN(x)) throw '请输入数字'; 14 var num = [7,8,9]; 15 for(var i=0; i<num.length; i++){ 16 if(x == num[i]){ 17 throw '该数字已经存在'; 18 } 19 } 20 if(x == 0){ 21 throw '输入不能为0'; 22 } 23 else if(x > 10){ 24 throw '数字太大了'; 25 } 26 else if(x <= 3){ 27 throw '数字太小了'; 28 } 29 else{ 30 y.style.color = 'green'; 31 y.innerHTML = 'OK'; 32 } 33 } 34 catch(e){ 35 y.innerHTML = '错误提示:' + e + '!'; 36 } 37 } 38 </script> 39 </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
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>
1 //setTimeout可实现统计: 2 <input type="text" id="demo" > 3 <script> 4 var num = 0; 5 function start() { 6 document.getElementById('demo').value = num; 7 num += 1; 8 setTimeout(start,1000); 9 } 10 setTimeout(start,1000); 11 </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() 方法执行的函数代码。使用该方法时关闭定时器时,在创建延迟定时器时必须使用全局变量。
开始、停止统计效果:
1 //开始、停止统计: 2 <input type="text" id="txt1" > 3 <input type="button" value="停止" onclick="stop()" > 4 <input type="button" value="开始" onclick="start()" > 5 <script> 6 var num = 0; 7 var time = null; 8 function start(){ 9 var oTxt = document.getElementById('txt1').value = num; 10 num += 1; 11 time = setTimeout('start()',1000); 12 } 13 start(); 14 function stop(){ 15 clearTimeout(time); 16 } 17 </script>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <div style="500px; margin: 0 auto;text-align:center;"> 9 <h2>页面错误:<span id="mydate" style="color:red;">3</span>秒后跳转到首页</h2> 10 <a href="index.html">首页</a> 11 </div> 12 <script> 13 my=setInterval(function(){ 14 //取得倒计时时间 15 mydate =document.getElementById('mydate'); 16 //修改倒计时时间-1 17 mydate.innerText=mydate.innerText-1 18 //到倒计时时间为0时,页面进行跳转 19 if(mydate.innerText == '0'){ 20 location.href='index.html' 21 clearInterval(my) 22 } 23 },1000) 24 </script> 25 26 </body> 27 </html>
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <input type="text"> 9 <input type="button" mytime=10 value="获取验证码" onclick="fun1(this)"/> 10 <script> 11 function fun1(obj){ 12 obj.setAttribute('disabled','disabled');//将当前元素修改为不可点击 13 mydate=obj.getAttribute('mytime')//取得当前倒计时总时间 14 obj.value='倒计时('+mydate+')秒';//用户点击按钮后第一次显示的文字 15 //创建一个计时器time。在匿名函数中执行操作 16 time=setInterval(function(){ 17 //取得当前mytime的属性值。mytime保存时倒计时时间 18 mydate=obj.getAttribute('mytime') 19 curtime=mydate-1; //当前的时间为每次取得当时就总时间-1 20 obj.setAttribute('mytime',curtime)//使用自定义属性mytime保存倒计时时间 21 obj.value='倒计时('+curtime+')秒';//修改按钮显示文字 22 if(curtime == 0){ //当前时间为0时。将元素恢复原状 23 clearInterval(time);//停止倒计时 24 obj.value='获取验证码';//修改倒计时按钮文字 25 obj.removeAttribute('disabled');//移除按钮不可触发的属性 26 obj.setAttribute('mytime',10);//设置默认倒计时时间 27 } 28 },1000); 29 } 30 31 </script> 32 </body> 33 </html>
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 表示没有按下。
1 document.onkeydown = function (){ 2 if(event.ctrlKey == 1){ 3 alert('Ctrl键被按了'); 4 } 5 else if(event.shiftKey == 1){ 6 alert('Shift键被按了'); 7 } 8 else if(event.altKey == true){ 9 alert('Alt键被按了'); 10 } 11 else{ 12 alert('都没被按下'); 13 } 14 };
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 <title>JavaScript示例</title> 6 <script> 7 window.onload=function (){ 8 var oTxt1 = document.getElementById('txt1'); 9 var oTxt2 = document.getElementById('txt2'); 10 var oBtn = document.getElementById('btn1'); 11 12 //点击按钮提交 13 oBtn.onclick = function (){ 14 //输入框的值等于文本框的值。 15 oTxt2.value += oTxt1.value + ' '; 16 //清空输入框的值,以便再次输入 17 oTxt1.value = ''; 18 }; 19 20 21 //按Enter或Ctrl+Enter提交 22 oTxt1.onkeydown = function (){ 23 //回车键的键码为13 24 if(event.keyCode == 13 || event.keyCode == 13 && event.ctrlKey){ 25 oTxt2.value += oTxt1.value + ' '; 26 oTxt1.value = ''; 27 } 28 }; 29 } 30 </script> 31 </head> 32 <body> 33 <input id="txt1" type="text" > 34 <input id="btn1" type="button" value="提交"><br> 35 <textarea id="txt2" rows="10" cols="50" ></textarea> 36 </body> 37 </html>
(4)、事件流
事件的传递有两种方式:冒泡与捕获。事件传递定义了元素触发事件的顺序。
事件冒泡:当一个元素发生事件后,事件会顺着层级(父级 - 父级的父级 --)关系依次向上传递直到 document。
事件捕获:事件捕获与事件冒泡正好相反,外部元素的事件会先被触发,然后才会触发内部元素的事件,即从祖先到后代。
事件流同时支持两种事件方式,冒泡型事件和捕获型事件,但是捕获型事件先发生。
两种事件流会触发 DOM 中的所有对象,从 document 对象开始,也在 document 对象结束。
语法:addEventListener('事件名称',函数,冒泡/捕获)
addEventListener() 方法用于向指定元素添加事件,该方法不会覆盖已存在的事件,可同时向一个元素添加多个事件。该方法有三个参数,第一个参数定义事件的类型,
第二个参数规定事件触发后调用的函数,第三个参数是布尔值,用于定义该事件是冒泡还是捕获,若为 false,则表示冒泡事件,若是 ture,则表示捕获事件。
这里需要注意是的该方法的事件类型,不需要加”on“,比如平时写点击事件:“onclick”,该方法中则使用“click”即可。
1 <!DOCTYPE html> 2 <html id="htm"> 3 <head> 4 <meta charset="utf-8" /> 5 <title>JavaScript示例</title> 6 <style> 7 div{padding:50px;} 8 </style> 9 <script> 10 window.onload = function (){ 11 var x = document.getElementById("div1"); 12 var y = document.getElementById("div2"); 13 var z = document.getElementById("div3"); 14 15 var o = document.getElementById("bod"); 16 var n = document.getElementById("htm"); 17 18 x.addEventListener("click", function() { 19 alert("1冒泡"); 20 }, false); 21 22 y.addEventListener("click", function() { 23 alert("2冒泡"); 24 }, false); 25 26 z.addEventListener("click", function() { 27 alert("3冒泡"); 28 }, false); 29 30 o.addEventListener("click", function() { 31 alert("body捕获"); 32 }, true); 33 34 n.addEventListener("click", function() { 35 alert("html冒泡"); 36 }, false); 37 }; 38 </script> 39 </head> 40 <body id="bod"> 41 <div style="background:lightgreen;margin-bottom:10px;">我是body元素,我捕获。祖先html也会冒泡。</div> 42 <div id="div3" style="background:#ccc;">我是div3,我冒泡 43 <div id="div2" style="background:green;">我是div2,我冒泡 44 <div id="div1" style="background:red;">我是div1,我冒泡</div> 45 </div> 46 </div> 47 </body> 48 </html>
removeEventListener() 方法用于移除由 addEventListener() 方法添加的事件监听。这里需要注意在绑定函数时不能使用匿名函数,否则无法删除。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>JavaScript示例</title> 6 <style> 7 #div1{ 8 background:green; 9 padding:50px; 10 color:white; 11 } 12 </style> 13 </head> 14 <body> 15 <div id="div1"> 16 div元素添加了onmousemove事件监听,鼠标在绿色区域内移动时会显示随机数。 17 <p>点击按钮可移除div的事件监听。</p> 18 <button id="btn1">点击移除</button> 19 </div> 20 <p id="p1"></p> 21 22 <script> 23 var oDiv = document.getElementById("div1"); 24 var oP = document.getElementById("p1"); 25 var oBtn = document.getElementById("btn1"); 26 27 oDiv.addEventListener("mousemove", block, false); 28 function block(){ 29 oP.innerHTML = Math.random()*10; 30 } 31 oBtn.onclick = function (){ 32 oDiv.removeEventListener("mousemove", block, false); 33 }; 34 </script> 35 </body> 36 </html>
cancelBubble 方法可取消事件冒泡,不会往父级传递。实例:仿百度翻译效果,点击显示按钮显示 div,随便点击页面其他位置隐藏 div。
如果不取消事件冒泡,则在点击按钮的同时 div 先是显示了,然后又立马被隐藏了,可以注释掉取消事件冒泡代码,用弹窗查看效果。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>JavaScript示例</title> 6 <style> 7 #div1{ 8 500px; 9 height:300px; 10 background:lightgreen; 11 display:none; 12 } 13 </style> 14 <script> 15 window.onload = function (){ 16 var oBtn = document.getElementById("btn1"); 17 var oDiv = document.getElementById("div1"); 18 19 //点击按钮显示div 20 oBtn.onclick = function (){ 21 oDiv.style.display = 'block'; 22 //alert('显示'); 23 24 //取消事件冒泡,不会往父级传递。 25 event.cancelBubble = true; 26 }; 27 28 //点击document隐藏div 29 document.onclick = function (){ 30 oDiv.style.display = 'none'; 31 }; 32 }; 33 </script> 34 </head> 35 <body> 36 <input id="btn1" type="button" value="显示"> 37 <div id="div1"></div> 38 </body> 39 </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 闭包,函数拥有自己的私有变量。