2021-08-10

web前端之javascript

闭包、立即执行函数
(1)闭包
先来举个例子:

例 :
function a(){ 
 function b(){ 
 var bbb = 234; 
 console.log(aaa); 
 } 
 var aaa = 123; 
 return b; } 
var glob = 100; 
var demo = a(); 
demo();

我们来看这个例子,全局有一个函数a,变量glob、demo然后a执行,执行前a的作用域链里面放的时a的AO和GO,然后在a函数里面有一个函数b和变量aaa,b 函数最开始拿的是 a 的劳动成果(a 的 AO 和 GO),但是最重要的是,b函数没有被执行,在 a 函数结束的时候把 b 函数返回了出去,在全局 var 一个 demo 等 于 a 执行,就是说现在把 a 函数的返回值赋给了 demo,也就是说 demo 里边现在装的是b 函数。好,a 函数现在执行完了,他要销毁自己的 AO,我们看着好像就连同 b 函数一块儿被销毁了,然而并没有,全局的 demo 现在就是里边的函数 b,而且手里还攥着 a的劳动成果,就是说虽然 a 把自己的 AO 销毁了,然而 b 手里还有 a 的 AO 和 GO 呢,而且还被保存到了外部的 demo 里,所以你在外边执行 demo,就等于执行函数 b,执行时要访问 aaa,先创建自己的 AO 放到作用域链最顶端,然后发现自己的 AO 里没有 aaa,然后往下找,找到 a 的 AO 有变量 aaa,然后输出得 123,我们视觉上看着好像 b 函数被保存到了外部,再去访问函数里的 aaa 好像是不行的,然而事实上是可以的,这个过程就叫做闭包。

注意:这里把b扔给demo仍的是引用值,就等于把b的地址扔给demo了,demo和b就相当于同一个人的不同名字,所以demo执行等于b执行。

闭包的产生:但凡内部函数保存到了外部,它一定生成闭包。例如上面的,内部函数会保存外部函数的劳动成果,然后还没执行呢,就通过return的方式保存到了外部,一保存到外部,那么a函数想删自己的AO都删不了了。因为b那里始终都有。

(2)闭包的应用

 例:
function a(){ 
 var num = 100; 
 function b(){ 
 num ++; 
 console.log(num); 
 } 
 return b; } 
var demo = a(); 
demo(); 
demo();

解析:a 执行时产生了自己的 AO 和 GO,然后 b 在定义的时候拿到 a 的劳动成果,a 函数最后把 b 返回给了 demo,然后 a 销毁自己的 AO,然而 demo 现在就是 b 函数,他还保存着 a 的 AO 和 GO,所以 demo 第一次执行的时候 b 先创建自己的 AO,然后自己的 AO里边没有 num,就去 a 的 AO 里拿,然后++,输出得 101,销毁自己的 AO,然后第二次执行时在创建自己的 AO,没有 num,然后去 a 的 AO 里拿,此时 num 是 101,再++,输
出得 102,这里每次执行创建和销毁的是 b 的 AO,然而里边并没有东西,他始终拿的是 a 的 AO 里的 num,a 的 AO 一直存在着。

(3)闭包的现象
当内部函数保存到外部时就会生成闭包,闭包会导致原有的作用链不释放,造成内存泄漏。

解释:例如上边的例 1,本来 a 执行完之后就会销毁自己的 AO,但是 b 被扔出来之后他说你销毁了也是白扯,我还拿着呢!所以他就会导致作用域链该释放的时候不释放,你有意做的闭包还好,假如闭包是无意做的,那就很占内存空间了。所以就会造成内存泄露,什么叫内存泄露?可以这么理解,你占得内存多了,剩余的内存就少了,我们关注剩余的内存,他是不是越来越少?就好像泄露了一样。所以这得反向的理解。

(4)闭包的作用
作用一:实现共有变量,比如函数累加器

例 :
function add(){ 
 var count = 0; 
 function demo(){ 
 count ++; 
 console.log(count); 
 } 
 return demo; } 
var counter = add(); 
counter(); 
counter(); 
counter(); 
……………

解析:我们用闭包就可以做一个函数累加器,在 add 里边先定义一个变量 count,然后定义一个函数 demo,demo 里实现 count++,最后把 demo 扔给全局的 counter,自此之后 counter 就会把 count 无限用了,你每调用一次 counter 他就会在原有的基础上加一次,这样做就更加模块化,你每执行一次方法他就会累加一次,结构化更好。

作用二:可以做缓存(存储结构)

 例:
function test(){ 
 var num = 100; 
 function a(){ 
 num ++; 
 console.log(num); 
 } 
 function b(){ 
 num --; 
 console.log(num); 
 } 
 return [a,b]; } 
var myArr = test(); 
myArr[0]();
myArr[1]();

解析:首先定义一个 test 函数,test 里边 var 一个 num 等于 100,还放着两个函数 a 和 b,a 函数里边让 num++,b 函数里边让 num–,然后在 test 最后通过数组的方式把a 和 b 都扔给外部的 myArr,这也可以形成闭包,你在下边执行 myArr 的第 0 位就等于a 函数执行,执行 myArr 的第 1 位就等于执行 b 函数,先执行 a 函数,a 拿的是 test的 AO,num++后输出得 101,再执行 b 函数,b 和 a 同级,拿的也是 test 的 AO,由于刚才 num 被 a++后得 101,所以再—输出得 100.

例 :
function eater(){ 
 var food = ""; 
 var obj = { 
 eat:function (){ 
 console.log("I am eating " + food); 
 food = ""; 
 }, 
 push:function (myFood){ 
 food = myFood; 
 } 
 } 
 return obj; } 
var eater1 = eater(); 
eater1.push("banana"); 
eater1.eat();

解析:首先定义一个 eater 函数,里边 var 一个 food 等于空串,然后定义一个 obj,他是一个对象,其实对象里边也可以有函数,只不过定义方法不一样,obj 里有一个eat,eat 里放的是函数,函数里先输出 I am eating 加上 food 里的内容,在让 food清空,然后还有一个 push 函数,函数里把形参 myFood 的值赋给 food,最后在 eater里边把 obj 返回,扔给全局的 eater1,这就相当于把里边的两个函数一起返回了,这也可以形成闭包,因为两个函数操作的都是同一个 food,先执行 push 并传参 banana,那么 myFood 就是 banana,最后在赋给 food,在执行 eat,此时 food 等于字符串banana,
所以输出得 I am eating banana,这里边的 food 就是一个隐式的存储结构,这就是缓存的一个应用。

作用三:可以实现封装、属性私有化

作用四:模块化开发、防止污染全局变量

立即执行函数

比如说我现在全局function a(){},function b(){},这两个函数假如说写在全局,
如果 JavaScript 不执行完,这两个函数永远都是等待被执行,永远释放不了,假如定义一个函数你只想用一次,后边就不用他了,他还一直写在全局不释放,这样的话就很占空间了,鉴于此,JS 给我们提供了一类函数,叫立即执行函数,这种函数执行完马上就释放了,是针对初始化功能的函数,不占用内存空间。

(1)形式:
(function (){

}())

先写一对小括号,然后里边写上 function,都不用起名,然后小括号大括号,最后来个小括号,他也是 js 里唯一一个执行完立即销毁的函数。他和普通函数除了执行完就被销毁之外没有任何区别。他也可以有参数,比如说:

(function (a,b){
console.log(a + b);
}(1,2))

输出就得 3,他同样也有返回值,
var num = (function (a,b){
var d = a + b;
return d;
}(1,2))

我们在外边 var 一个 num 接收返回值,那么,num 就是 3.

(2)写法:
第一种:(function (){}()) W3C 组织建议这一种写法
第二种:(function (){})()

(3)拓展:
只有表达式才能被执行符号执行。
比如说:
function test(){ console.log(1);
}()

这么写系统会报错,因为这个是函数声明不是表达式,你必须在底下单独写 test();下边写的 test 才叫做表达式,像 123、234、1 + 2 这都叫表达式,再比如:

var test = function (){ console.log(1);
}()

这么写就可以执行,因为它本身就是函数表达式。能被执行符号执行的表达式他的函数名就会被自动放弃,也就是说能被执行符号执行的表达式他就成了立即执行函数,就像上边的,你在后边加了括号就是立即执行,他就成了立即执行函数。再比如:

  • function test(){
    console.log(1);
    }()

本来他是函数声明,但是你在前边加上正号他就理论上转换为数字了,那他就是一个表达式,你再后边直接加()就会执行,变成立即执行函数,然后忽略函数名,你执行完后输出 test 就会报错,当然前边加上-或者!都可以,&&和||也可以,不过两边都要有东西,但是*和/不行,这里的±代表正负号。再比如:

(function test(){
console.log(1);
}())

我们如果在外边加一个小括号,他也就成了一种表达式,因为我们计算的时候,有时候有小括号,那么,他就会自动放弃函数名,后来人们一看,有没有函数名都无所谓,所以后来就去掉了,成了现在的立即执行函数(执行符号()放在括号里边或者外边都可以,W3C 组织建议放在里边)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值