四、Function对象
1. 匿名函数
- 什么是:定义函数时,不指定函数名的函数
- 为什么:2大优点
- 节约内存
- 避免产生全局变量,造成全局污染
- 何时用:2种情况
- 几乎所有回调函数都要定义为匿名函数
- 匿名函数自调用
- 如何用:2种情况
- 回调函数
- 什么是:自己定义了函数,但是不由自己调用,而是交给另一个函数,由另一个函数按需自动调用-----给别人用的函数
举例1:想将一个数字内容的数组升序排列arr.sot(function(a,b){return a-b})
补充:
sort() 方法,有一个可选参数,必须是函数,供它调用。那么就是个回调函数咯!回调函数的参数要有两个:第一个参数的元素肯定在第二个参数的元素前面!!!这个方法的排序是看回调函数的返回值: 如果返回值大于 0,则位置互换。 如果返回值小于 0,则位置不变。
举例2:想根据不同的敏感词,动态选择不同的新值替换str=str.replace(/正则表达式/,function(keyword){return 不同新值})
- 为什么回调函数都要定义为匿名函数:为了节约内存!
- 什么是:自己定义了函数,但是不由自己调用,而是交给另一个函数,由另一个函数按需自动调用-----给别人用的函数
- 匿名函数自调:
- 什么是:定义一个匿名函数后,立刻调用该函数执行,调用后立即释放
- 为什么:避免产生全局变量,造成全局污染
- 何时用:今后一切js代码都应该放在一个大的匿名函数自调内!尽量不要使用全局变量!
- 如何做:
- 标准写法:
创建新函数,立刻调用执行var 变量名=(function(形参变量列表){ ...return 返回值 })(实参值列表);
因为该函数没有名字,所以调用后,立刻释放 - 非主流写法:
+function(){...}()
!function(){...}()
…
强调:结尾的(),必须要加,表示调用
- 标准写法:
- 示例:使用匿名函数自调避免全局污染
输出结果:<script> // 全局变量,记录程序执行的总时间 var t=0; // 第一段程序:执行0.3s !function(){ t+=0.3; console.log(`任务一耗时0.3s`); }(); // 假设之后在此处添加了以下代码,这里出错了,需要记录出错时间 (function(){ // 局部变量,碰巧和全局变量重名了,也不会影响到全局变量 var t=new Date(); console.log(`这里出错了,在${t.toLocaleString()}`) })(); // 第二段程序:执行0.8s +function(){ t+=0.8; console.log(`任务二耗时0.8s`); }(); console.log(`共耗时${t}s`); </script>
任务一耗时0.3s
这里出错了,在2020/5/4 下午8:21:04
任务二耗时0.8s
共耗时1.1s
- 回调函数
2. 作用域和作用域链
- 什么是作用域(scope):
- 从作用是说:作用域就是一个变量的可用的范围
- 从本质上说:作用域其实是保存变量的特殊对象
- 为什么用作用域:为了避免不同范围之间的变量互相干扰
- JS中包括2级作用域
-
全局作用域
- 什么是:在程序任何位置都可以随意访问的,专门保存全局变量的存储空间
- 在浏览器中,全局作用域由window对象担当
- 当打开网页的一刹那,浏览器就会自动提前创建window对象,等待着保存后续创建的所有全局变量
- 我们创建的所有全局变量都默认保存在window对象中
- 保存在window对象中的变量,可在程序的任何位置被访问到
- 示例:验证全局变量到底存在哪里
输出结果:<script> // window var a=10; var fun=function(){console.log(1)}; var arr=[1,2,3]; console.log(a); console.log(fun); console.log(arr); console.log(window); console.log(window.a); console.log(window.fun); console.log(window.arr); </script>
10
ƒ (){console.log(1)}
(3) [1, 2, 3]
Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
10
ƒ (){console.log(1)}
(3) [1, 2, 3]
执行过程:
-
函数作用域
-
什么是:专门保存仅函数内可用的局部变量的存储空间
-
JS中一个函数的作用域不调用是不存在的!只有调用函数时,才临时创建该函数的作用域,临时保存局部变量,函数调用后,函数作用域对象以及本次函数调用使用的局部变量一同释放。
-
行数作用域对象创建和释放的过程----4步
-
当定义一个函数时:
都会创建一个函数对象,在每个函数对象上都会保存一个作用域链(好友列表)
-
当调用函数时:
- 会临时创建一个函数作用域对象
- 在函数作用域对象中添加函数中所需要的的所有局部变量:这里的局部变量包含2种情况
a. 在函数内var出的变量
b. 函数的形参变量 - 将函数作用域对象的地址,临时保存到函数作用域链(好友列表)中离函数近的格子中
-
函数执行过程中:
函数会按照先局部,在全局的顺序,使用变量
如果局部有要用的变量,则优先使用局部的变量
除非函数作用域中没有要用的局部变量,才被迫去下一级作用域链—即此处的window中找变量使用
-
调用函数后:
函数会释放函数作用域对象,导致函数作用域对象中的所有局部变量一同释放,所以,所有局部变量都不可重用
-
-
示例:判断程序的输出结果
<script> //例子1: // var a=10; // function fun(){ // var a=100; // a++; // console.log(a); // } // fun();//101 // console.log(a);//10 //例子2: // var a=10; // function fun(){ // a=100; // a++; // console.log(a); // } // fun();//101 // console.log(a);//101 //例子3: var a=10; // 此处a为形参,函数调用完就释放了 function fun(a){ a=100; a++; console.log(a); } fun();//101 console.log(a);//10 </script>
-
-
- 作用域链(scope chain)
-
什么是:由多级作用域对象链接起来组成的数据结构,就叫作用域链----即上面的好友列表
-
保存了一个函数可用的所有变量
-
控制着变量的使用顺序:先局部,后全局
-
3. 闭包
-
什么是闭包:
- 从作用上来说:既重用一个变量,又保护变量不被污染的一种编程方式
- 从本质上来讲:外层函数的作用域对象,被内层函数引用着而无法释放,就形成了闭包对象
-
为什么要用闭包:因为js中,全局变量和局部变量都有不可兼得的优缺点
- 全局变量:
优点:可重用;缺点:极易被污染和篡改----开发中几乎禁止使用全局变量 - 局部变量:
优点:因为只能在函数内使用,出了函数就用不了,绝对不会被篡改;缺点:不可重用
- 全局变量:
-
何时用:今后只要想重用一个变量,而且还想保护这个变量不会被别人篡改是,都要用闭包!
-
如何用:3步
- 用外层函数包裹要保护的变量和内层函数
- 外层函数将内层函数对象返回到外部
- 想使用内层函数的人,调外层函数,就可获得返回出来的内层函数的对象,再保存在变量中。就可反复使用内层函数对象了
-
结果:外层函数与内层函数之间的这个被保护的变量,既可以反复使用,又不会被篡改
-
示例:需求定义函数,为小孩保管压岁钱
<script> // 需求:定义一个函数,为小孩保管压岁钱 // 小孩儿每花一笔钱,可以从总钱数中扣除花的钱,提示还剩xxx // 3步 // 1. 定义一个外层函数,包裹要保护的变量和内层函数 function parent(){ var total=1000; // 2. 外层函数将内层函数对象返回到函数外部,让外部可用 // 步用给内层函数起名字 return function(money){ total-=money; console.log(`花了${money},还剩${total}`) } } // 3. 想用内层函数的人需要调用外层函数,才能获得返回出来的内层函数,保存在变量中,反复使用 var pay=parent(); // 试图篡改total变量 total=0; console.log(total);//输出0,但是不影响函数内的局部变量total pay(100);//花了100,还剩900 pay(100);//花了100,还剩800 pay(100);//花了100,还剩700 </script>
-
闭包原理:笔试题:一句话概括闭包是如何形成的
外层函数调用后,由于外层函数的作用域对象被内层函数对象引用着,无法释放形成了闭包!
-
闭包的缺点:比一般的函数多占用一块内存空间----多占外层函数的作用域对象(闭包对象)
-
解决方法:当闭包不在使用时,要尽早释放闭包!
pay=null;
----内存函数对象被释放,导致外层函数的作用域也被释放` -
笔试题:如何判断闭包问题的输出:找2个东西
-
找处于外层函数和内层函数之间的变量----要保护的变量
-
找外层函数共向外跑出来几个内层函数对象:3种情况
- 通过return返回一个内存函数
- 通过给全局变量赋值,将内层函数,赋值到外部的变量中
- 还是用return或赋值的方式。返回一个数组或一个对象,但是数组或对象中包含函数成员:
例如:var arr=[]; arr[0]=function(){...}; arr[1]=function(){...}; arr[2]=function(){...}; return arr;//多黄蛋
-
结论:包裹的多个内层函数共用一个受保护的变量----妈妈一次生的多个孩子共用同一个受保护的变量
-
示例:画简图分析闭包程序的输出结果:
<script> function fun(){ var n=999; // js语法规定:任何时候给一个未声明过的变量赋值,不会报错!而是自动在全局创建该变量 nAdd=function(){n++}; return function(){ console.log(n); } } var getN=fun(); getN();//999 nAdd(); getN();//1000 </script>
-
如果第二次调用外层函数:则会创建全新的一个闭包对象和变量,重新生成一组内层函数对象。与第一次调用外层函数生成的闭包,毫无关系。
-
总结: Function
- 创建函数: 3种:
- 声明方式: 缺点: 会被声明提前
function 函数名(形参变量列表){ 函数体; return 返回值 }
- 赋值方式----函数表达式: 优点: 不会被声明提前
var 函数名=function (形参变量列表){ 函数体; return 返回值 }
- 用new: 几乎不用
var 函数名=new Function("形参1", "形参2",...,"函数体; return 返回值")
- 声明方式: 缺点: 会被声明提前
- 重载:
- 何时: 一件事可根据传入的实参值不同,可自动选择执行不同的逻辑
- 如何:
function 一个函数( 不要形参 ){ // arguments{ , , , }.length // 0 1 2 ... //通过判断arguments中的实参值个数或实参内容不同,来决定执行何种逻辑 }
- 匿名函数:
- 何时: 回调函数 以及 匿名函数自调
- 匿名函数自调:
- 今后几乎所有js代码都要放在匿名函数自调中。避免使用全局变量
- 如何:
a. 标准写法:
b. 杀马特写法:var 返回值=(function(形参列表){ 函数体; return 返回值; })(实参值列表);
+function(){ ... }()
!function(){ ... }{}
... ...
- 作用域和作用域链:
- Js中只有两种作用域:
- 全局作用域对象window:
1). 自动保存所有全局变量
2). 在程序的任何位置都可访问 - 函数作用域对象:
- 只在调用函数时创建,调用后自动释放
- 函数作用域对象中专门保存函数的局部变量: 2种:
i. 形参
ii. 函数内var出的变量
- js中和其它语言的差别之一: 没有块级作用域:
1). 什么是块作用域: 分支结构(if else if else)或循环结构(for while do while switch)的{},在其它编程语言中称为块级作用域
2). 在其它编程语言中,分支结构或循环结构{}内声明的变量,出了{},不能使用!
3). Js中,因为没有块级作用域,所以,在分支结构或循环结构{}中声明的变量,出了{},依然可以使用
4). 示例: 有没有块作用域情况下,程序不同的写法<script> // js中 // 如果随机生成一个数是偶数 if(parseInt(Math.random()*10)%2==0){ var result="偶数"; }else{ var result="奇数"; } console.log(result); // 在其他有块级作用域的语言中 var result=""; if(parseInt(Math.random()*10)%2==0){ result="偶数"; }else{ result="奇数"; } console.log(result); // js种 // 求1~100的所有数字的和 for(var i=1,sum=0;i<=100;i++){ sum+=i; } console.log(sum); // 在其他有块级作用域的语言中 var sum=0; for(var i=1;i<=100;i++){ sum+=i; } console.log(sum); </script>
- 全局作用域对象window:
- 作用域链:
- 每个函数对象上其实都保存着一个作用域链列表
- 普通函数作用域链中都有两个格子:
- 离自己近的格子,不调用函数时,暂时为空,当调用函数时,会引用临时创建的函数作用域对象
- 离自己远的格子始终保存window
- 作用域链保存着当前函数所有可用的变量
- 作用域链控制着变量的使用顺序: 先局部,后全局
- Js中只有两种作用域:
- 闭包:
- 什么是闭包: 外层函数调用后,外层函数的作用域对象,被内层函数引用着无法释放,形成了闭包对象.
- 为什么: 全局变量和局部变量都有不可兼得的优缺点:
- 全局变量: 优点: 可重用; 缺点: 极易被污染——今后极力避免使用全局变量
- 局部变量: 优点: 不会被外部篡改; 缺点: 不可重用
- 何时: 希望既可重用一个变量,又想保护该变量不会被随意篡改时
其实是想给一个函数或一组函数保存一个专属的变量 - 如何: 3步:
- 用外层函数包裹要保护的变量和要使用变量的内层函数
(注意: 如果内层函数中没有用到外层函数的局部变量,则不会形成闭包)function outer(){ var i=0; function fun(){ console.log(a) } } )
- 外层函数将内层函数对象返回到外层函数外部
- 想使用内层函数的人应该调用外层函数获得内层函数对象,并保存在变量中,可反复使用
- 用外层函数包裹要保护的变量和要使用变量的内层函数
- 结果: 外层函数的局部变量,只归内层函数专属,其他人无法修改受保护的变量。
- 一句话概括闭包如何形成: 外层函数调用后,外层函数的作用域对象,被内层函数引用着,无法释放,形成了闭包。
- 闭包缺点: 比一般的函数,多占用一块内存——外层函数的作用域对象
- 解决: 将保存内层函数的变量=null
结果: 释放了内层函数,导致内层函数引用的外层函数作用域对象被释放 - 笔试题: 画简图:
- 找外层函数和内层函数之间的局部变量。——要保护的变量
- 找外层函数妈妈共向外生出了几个内层函数孩子: 在外层函数内3种途径
return function(){ ... }
全局变量=function(){ ... }
- return arr或obj,但是arr或obj中,可能包含函数成员
- 结果:
- 妈妈一次生出的多个孩子,共享同一个闭包变量,一个函数修改了闭包变量,则另一个函数也受影响----即外层函数内包裹的多个内层函数共用一个闭包变量
- 妈妈分两次生的不同的孩子和闭包变量之间,互不影响,毫无关系。-----即多次调用外层函数创建的不同变量之间,闭包变量毫无关系,不共享