js函数、函数作用域、函数作用域链
二. Function
1. 复习:
(1). 什么是:程序中专门保存一段可反复使用的代码段的程序结构,再起一个名字
(2). 为什么:代码重用
(3). 何时:今后,只要一段代码,可能被多处反复使用时,都用函数。
(4).如何:
a. 定义函数:
function 函数名(形参列表){
函数体;
return
返回值
} //强调:函数,不调用不执行!
b. 调用函数:
var 变量=函数名(实参值列表);
(5). 形参:
a. 什么是:专门接收函数执行时所必须的数据的变量
b. 为什么:函数执行时,有的数据可能会变化,或不确定!就需要用一个变量,临时占位!
c. 何时:今后,只要定义一个函数时,发现有些数据暂时不确定,或将来可能发生变化,都用形参占位!
d. 凡是定义了形参变量的函数,在调用时都要传入实参值。原则上,实参值的个数和顺序,要和形参列表保持一致!
(6). 返回值:
a. 什么是:一个函数的执行结果
b. 为什么:默认情况下,函数内部的变量和数据只能在函数内使用,出了函数,无法使用
c. 何时:今后,只要出了函数之后,还想用函数的执行结果时,都必须定义返回值。
(7). 总结:
a. 如果函数自身包含不确定的值,就用形参
b. 如果函数外部需要用函数的执行结果时,就用返回值
(8). 示例:定义函数对数组求和
3_fun.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> //想定义一个函数,对任意一个数组中的所有元素值,求和 function sum(arr){ //数组求和固定套路 //1. 下定义一个变量保存临时汇总值 var result=0; //2. 遍历数组中每个元素值 for(var i=0;i<arr.length;i++){ //每遍历一个元素值,就将元素值累加到result变量上 result+=arr[i]; } //3. 遍历结束,返回result变量中的和 return result; }
var arr1=[1,2,3]; var arr2=[1,2,3,4,5]; //还想对每个数组中的元素值求平均 var result1=sum(arr1); var result2=sum(arr2); console.log(`和:${result1},平均:${result1/arr1.length}`); console.log(`和:${result2},平均:${result2/arr2.length}`); </script> </body> </html> 运行结果: 和:6,平均:2 和:15,平均:3 |
2. 创建函数三种方式:
(1). 声明方式:
a. function函数名(形参列表){
函数体;
return 返回值;
}
b. 问题:会被声明提前(hoist),会打乱程序正常的执行顺序
c. 什么是声明提前:
1). 在程序开始执行前
2). js引擎会先扫描所有声明语法:2种:
i. var 变量名;
ii. function 函数名( ... ){ ... }
3). 将所有声明语法提前到当前作用域的顶部集中执行
4). 赋值留在原地。
这个是在nodejs中提到过的变量提升,会将var声明的变量与function声明的函数提前到函数作用域的最前面
(2). 赋值方式:
a. var 函数名=function(形参列表){ 函数体};
b. 优点:因为不符合声明语法,所以,不会被声明提前。保证了程序顺序执行!
这种方式变量提升的不是函数而是变量函数名
c. 今后,几乎所有的函数定义,都首选赋值方式!
d. 示例:定义函数解决声明提前的问题:
4_hoist.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> // function fun(){ console.log(1) } // fun();//2 // function fun(){ console.log(2) } // fun();//2
var fun=function(){ console.log(1) } fun();//1 var fun=function(){ console.log(2) } fun();//2 </script> </body> </html> 运行结果: 1 2
|
(3). 用new: 几乎不用
a. var 函数名=new Function("形参1", "形参2", "函数体");
b. 揭示: js中,函数也是一个对象。
1). 函数也可以像数组和普通对象一样赋值给其它变量!
2). 函数也可以像数组和普通对象一样作为实参值传入到另一个函数中。
函数也是对象,这种方式是创建一个对象的实例的方式,但是基本不使用
c. 示例:演示函数赋值和将函数作为参数传递(回调函数)
5_fun2.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> var a=function(){ console.log("疼!") }; a(); var b=a; // var b=a(); //只是将a()调用后的执行结果给b,并不是把a函数本身给b b();
var arr=[3,23,1,12,123,2]; arr.sort(function(a,b){return a-b}); console.log(arr); </script> </body> </html> 运行结果: 疼! 疼! (6) [1, 2, 3, 12, 23, 123]
|
3. 重载:
定义:
重载是同名函数不同参数,这种称为重载;重载定义了不同情况下的同一事件的处理方式
(1). 问题:如果一件事,有多种执行情况时,如果为每种情况分别定义一个函数,那么函数的个数会很多!
(2). 解决:今后,只要一件事,可能根据传入的实参值不同,而执行不同的逻辑时,都要用重载!
(3).什么是:相同函数名,不同形参列表的多个函数,在调用时,可以自动根据传入实参值列表的不同,自动选择对应版本的函数执行!比如: Java中:
问题: js语言不允许多个同名函数同时存在
js语言不允许多个同名函数同时存在。如果同时存在多个同名函数,结果只有最后一个函数可以幸存下来,覆盖之前所有同名的函数。
解决:使用自带参数列表arguments,判断参数列表的长度
变通实现的:
a. 只定义一个函数,且不要定义任何形参变量
b. 在函数中arguments对象会帮我们自动接住所有传入函数的实参值。
1). 什么是arguments:
i. 每个函数中自带的- 不用自己创建,直接就可用!
ii. 专门接收所有传入函数的实参值的- 内容
iii. 类数组对象- 存储结构
2). 什么是类数组对象:
i. 长得像数组的对象
ii. 相同点:下标, length, 可以用for遍历
iii. 不同点:类型不同,arguments无法使用数组类型的任何一个函数。
c. 根据传入的实参值个数(arguments.length)不同,判断执行不同的逻辑
d. 通过arguments[下标]方式,获取本次调用时,传入的一个实参值。
(6). 示例:定义一个函数支持三种付款方式:
6_overload.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> //定义函数,实现不同的付款方式 function pay( ){ // ↓ ↓ ↓ ↓ // arguments[ ].length // 参数们 0 1 2 3 ... //如果没有传入实参值,就手机支付 if(arguments.length==0){ console.log(`手机支付...`) }else if(arguments.length==1){//否则如果只传了一个实参值,就现金支付 console.log(`现金支付,收您${arguments[0]}元...`); }else{//否则,就刷卡支付 if(arguments[1]=="123456"){ console.log(`刷卡支付,从您卡号${arguments[0]}中扣款成功...`) }else{ console.log(`刷卡支付,密码错误,请重试...`) } } }
pay(); pay(100); pay("6553 1234", "12345"); pay("6553 1234", "123456"); </script> </body> </html> 运行结果: 手机支付... 现金支付,收您100元... 6刷卡支付,密码错误,请重试... 刷卡支付,从您卡号6553 1234中扣款成功... |
4. 匿名函数:
(1). 什么是:定义函数时,没有指定函数名的函数
(2). 分2种情况:回调函数,自调用函数
a. 回调函数:
1). 什么是:我们定义的函数,不是我们自己调用,而是交给别的函数或程序去调用!
2).
比如:
i. arr.sort(function(a,b){return
a-b});
ii. setTimeout(function(){ ... }, 1000);
iii. xhr.onreadystatechange=function(){ ... }
3). 为什么回调函数,多数都是匿名函数?节约内存!因为匿名函数没有变量引用,用完立刻释放!
b. 匿名函数自调
1). 什么是:创建一个匿名函数后,立刻调用这个函数
2). 问题:大型项目中,不同功能之间的变量,极有可能互相干扰!——全局污染!
3). 何时:原则上每个功能的代码和所用的变量,都应该包裹在一个匿名函数自调中。
4). 如何:
(function(){ ... })();
5). 示例:定义两个银行的取号程序,避免两个银行的号互相干扰。
7_anonyFun.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> //建设银行 (function(){ var i=0; function getNum1(){ i++; console.log(`建设银行${i}号`) } setInterval(function(){ getNum1(); },1000); })();//立刻调用!
//农行 (function(){ var i=0; function getNum2(){ i++; console.log(`农行${i}号`) } setInterval(function(){ getNum2(); },2000); })(); </script> </body> </html> 运行结果: 建设银行1号 建设银行2号 农行1号 建设银行3号 建设银行4号 农行2号 建设银行5号 |
5. 作用域和作用域链:
(1). 什么是作用域:
a. 一个变量的可用范围。
b. 其实,作用域也是一个对象。是专门保存各种变量的特殊对象。
(2). 为什么:为了避免不同范围的变量之间互相干扰。(全局污染)
(3). js中共包括2级作用域:
a. 全局作用域:
1). 什么是:任何代码都可随意访问到的范围。
2). 在全局作用域中定义的变量,称为全局变量。
b. 函数作用域:
1).什么是:仅在一个函数内可用的范围
2). 函数作用域中保存着函数的所有局部变量,只有2种情况:
i. 在函数内var出的变量
ii. 函数的形参变量也是局部变量
(4). 示例:作用域练习:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> var a=10; function fun1(){ var a=100; a++; console.log(a);//101 } fun1(); console.log(a);//10
var b=10; function fun2(){ b=100; b++; console.log(b);//101 } fun2(); console.log(b);//101
</script> </body> </html> 运行结果: 101 10 101 101 |
(5). 作用域的原理:
做菜4步:找菜谱,备料,做菜,收拾厨房
a. 开局:
1). 创建各种全局变量和全局函数:
所有全局变量和全局函数默认都保存在window对象中的
window对象就是全局作用域对象
注意:window的全局作用域是在前端的浏览器中执行js时的全局作用域,在使用nodejs执行时使用的全局作用域是global
2). 每个函数对象身上,都有一个好友列表。
普通函数的好友列表中有两个格子。
离函数近的格子暂时为空
离函数远的格子保存window对象的地址.
b. 调用函数时:做菜三部曲:备料,做菜,收拾厨房
1). 调用函数前:
i. 临时创建函数作用域对象,保存函数中的所有局部变量。
ii.并将函数作用域对象的地址,保存到"好友列表"中离函数近的格子里。
2). 函数调用过程中:
js引擎按函数中记录的步骤,执行操作。
操作过程中,只要用到任何变量,都会先在离函数近的函数作用域中查找
如果函数作用域对象中没有,才被迫去全局window对象中查找。
3). 函数调用后:
js引擎自动释放函数作用域对象
函数作用域对象中的所有局部变量一同被释放。
所以,函数作用域中的所有局部变量,在函数执行后,就不存在了。
(6). 作用域链:
a. 定义
由多级作用域对象串联形成的结构
b.作用
保存着一个函数可用的所有作用域和变量
控制着变量的使用顺序: 就近原则,先局部后全局
6. 闭包:
(1). 问题:
全局变量和局部变量都有不可兼得的优缺点
a. 全局变量:
1). 优点:可反复使用
2). 缺点:极容易被篡改
b. 局部变量:
1). 优点:不会被篡改
2). 缺点:不可重用!
(2).解决:
今后,只要希望重用一个变量,又保护变量不被篡改时,都必须用闭包!
(3).步骤方案: 3步:
a. 用外层函数包裹内层函数和要保护的变量
b. 将内层函数返回到外层函数外部
c. 外层函数外部,必须调用外层函数,才能获得返回的内层函数对象,保存在变量中,反复使用。
将目标函数作为函数属性将其对象返回给父函数作为父函数的对象,父函数可以自行定义自己的属性参数;最后将父函数的对象给一参数,称为闭包;闭包函数中目标函数的对象经过两次传递,函数的参数无法从外部函数进行修改,原理是局部变量的使用
关键是目标函数对象的返回
(4). 示例:使用闭包定义函数,帮小孩儿保管压岁钱
9_closure.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> //想定义一个函数,帮小孩儿管理零花钱 //1. 用外层函数包裹内层函数和要保护的变量 function mother(){ var total=1000;//全局变量 //2. 将内层函数返回到外层函数外部 return function(money){ total-=money; console.log(`花了${money},还剩${total}`); } } //3. 外层函数外部,必须调用外层函数,才能获得返回的内层函数对象,保存在变量中,反复使用。 var pay=mother(); //pay:function(money){ // total-=money; // console.log(`花了${money},还剩${total}`) //} pay(100); //别人的程序 total=0; pay(100); </script> </body> </html> 运行结果: 花了100,还剩900 花了100,还剩800 |