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 组织建议放在里边)