博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript之深入函数(二)
阅读量:5048 次
发布时间:2019-06-12

本文共 4220 字,大约阅读时间需要 14 分钟。

  上一篇我们主要讲解了函数的执行过程和原理,本篇我们将介绍函数的另外两个特殊表现:闭包和立即执行函数。

 

一   闭包

  1,  闭包的形成

  之前我们提到,函数执行完毕,马上就会销毁自己的AO对象。但是如果遇到下面这种情况:有子函数的定义,并将子函数返回。它真的就完全销毁了自己的AO对象吗?

1 function fn(){2     var a = 1;3     function son(){4          console.log(a);5     }6     return son;7 }8 var test = fn();9 test();//error ? 1

  这将打印什么呢?表面上看,son内并没有变量的声明,consol.log()访问a应该抛出错误。

  但事实上,test()将打印1,这是为什么呢?回忆上一篇文章函数的作用域链,不难发现:

    当fn执行时:fn.[[scope]] --- {0:AO(fn),1:GO};执行期间son被声明:son.[[scope]]  --- {0:AO(fn),1:GO};

  return son将保留该属性,这时fn已经执行完毕:

    fn.[[scope]] --- {0:GO}(AO(fn)被销毁?);

  直到test()执行时,test.[[csope]]  --- {0:AO(son),1:AO(fn),2:GO}( test是son的另一个引用,实际上他们是同一个函数)。

  这时test想要访问变量a,那么他将先在自己的AO内查找,没有,那么他将到fn的AO里去查找,刚好有,所以最终打印的是1。

       这里看似已经被fn销毁的AO(fn),实际上还被son引用着,所以它并没有真正的被完全销毁,只是对于fn来说,已经丢弃了对这个对象的引用,看起来像被销毁了。这个还被son保留着的AO对象我们即称之为闭包。闭包能帮助一个函数读取另一个函数内部的变量,它起到了连接两个函数的桥梁作用。

         总结一下,在JavaScripe中形成闭包需要三个要素:

    1,  父函数内定义了子函数。

    2,  子函数内访问了父函数的变量。

    3,  子函数被返回。

 

  2,闭包的应用

    a) 变量私有化,但可以实现全局变量的效果

1 function add(){ 2     var count = 0; 3     return function (){ 4         count ++; 5         //some code 6         console.log(count); 7     } 8 } 9 var myAdd = add();10 myAdd();//111 myAdd();//212 myAdd();//3

    b) 用作(类似)缓存

1 function person(){ 2     var money = 0; 3     var obj = { 4         pay:function (){ 5             if(money > 0){ 6                 console.log("I spent one yuan."); 7                 money --; 8             }else{ 9                 console.log("I run out of my money.");10             }11         },12         make:function (){13             console.log("I made one yuan.");14             money ++;15         }16     };17     return obj;18 }19 var person1 = person();20 person1.pay();//"I run out of my money."21 person1.make();//"I made one yuan."22 person1.pay();//"I spent one yuan."

    c) 模块化开发,防止全局变量污染

1 var a = "Global"; 2 function p0(){ 3     console.log(a); 4 } 5 function p1(){ 6     var a = "p1"; 7     return function(){ 8         console.log(a); 9     };10 }11 function p2(){12     var a = "p2";13     return function(){14         console.log(a);15     };16 }17 var myP1 = p1();18 var myP2 = p2();19 20 p0();//"Global"21 myP1();//"p1"22 myP2();//"p2"

  大型项目一般都是多人协同开发,每个人负责不同的模块,不可避免的,大家可能使用了相同的变量名,这将造成全局变量污染。使用闭包,即可解决这个问题题。了解了下一节的立即执行函数,这段代码还可以加以优化。

 

二     立即执行函数

         在认识立即执行函数之前,让我们先来了解执行符()的两个特点

         1)只有表达式才能被()执行。

1 function test(){2     console.log(1);3 }();//error 这是函数声明4 var test = function (){5     console.log(1);6 }();//1 这是函数表达式

         2)能被()执行的表达式会被系统忽略函数名称。

1 var test = function (){2     console.log(1);3 }();//14 console.log(test);//undefined5 //这是一个有趣的现象:我们声明了变量test,并把一个函数赋值给它,紧接着使用()执行了这个表达式,随即打印出了1。
按理说,这时test的值应该是一个匿名函数的函数体才对,但实际上它是undefined,变量刚被声明的状态,即系统放弃了变量test对函数的引用。

  这很好的印证了()执行符的第二个特点。

  1,立即执行函数的形式

  我们知道"()"括号实际上也是一种数学运算符,表示运算优先级的。那么我们当然可以把函数声明用括号包起来,使它成为一个表达式。这样我们就可以使用()执行符马上执行它并得到函数执行的结果了。

1 (function test(){2     console.log(1);3 });//函数声明变成了表达式4 //结合()执行符的第二个特点,我们可以将它优化5 (function (){6     console.log(1);7 })();//1

  以上就是立即执行函数的最终形式。另外,把()执行符放在函数声明的括号里面其实也是可以的。

1 (function (){2     console.log(1);3 }());//1

  2,立即执行函数的特点

  知道了()执行符的特点,其实我们不难发现:

    1)立即执行函数被声明后会马上执行函数体内的代码。

    2)执行完毕后立即销毁,不会被一直保存在内存中。

    3)只能被执行一次,不能起到代码块复用的功能。

  除了上述特点外,立即执行函数和普通函数的功能完全一样。

  3,立即执行函数结合闭包的经典应用

1 function fn(){ 2     var arr = []; 3     for(var i = 0; i < 10; i++){ 4         arr[i] = function () { 5             console.log(i); 6         }; 7     } 8     return arr; 9 }10 var myArr = fn();11 myArr.map(function (item){12     item();13 });//10 10 10 10 10 10 10 10 10 10

  我们是想依次输出1--9啊!为什么跑出来10个10呢?

  仔细想一想,不难发现,这是因为所有子函数和fn形成的是同一个闭包,所以最后都打印了10,那么要怎样才能实现我们想要的功能呢?

1 function fn(){ 2     var arr = []; 3     for(var i = 0; i < 10; i++){ 4        (function (j){ 5             arr[j] = function () { 6                 console.log(j); 7             } 8        }(i)); 9     }10     return arr;11 }12 var myArr = fn();13 myArr.map(function (item){14     item();15 });//0 1 2 3 4 5 6 7 8 9

  通过利用立即执行函数定义完即被执行的特点,每次循环的时候都把i的值当做立即执行函数的实参传递进去,使每个立即执行函数都和子函数形成单独的闭包,那么最终子函数在执行时访问到的其实都是各自闭包(立即执行函数的AO)里的i的值了。这样就得到了我们想要的结果了。

 

  虽然闭包能在很多地方发挥很大作用,但闭包也有它自身的缺陷:闭包将一直占用内存空间,严重时将导致内存泄漏,甚至系统崩溃。所以我们应该尽量避免使用闭包,如果别无他法,也应该在使用完后手动的解除它对内存的占用,比如把引用返回函数的变量赋值为null。

 

转载于:https://www.cnblogs.com/ruhaoren/p/11474076.html

你可能感兴趣的文章
iOS 8 地图
查看>>
20165235 第八周课下补做
查看>>
[leetcode] 1. Two Sum
查看>>
iOS 日常工作之常用宏定义大全
查看>>
PHP的SQL注入技术实现以及预防措施
查看>>
MVC Razor
查看>>
软件目录结构规范
查看>>
Windbg调试Sql Server 进程
查看>>
linux调度器系列
查看>>
mysqladmin
查看>>
解决 No Entity Framework provider found for the ADO.NET provider
查看>>
SVN服务器搭建和使用(三)(转载)
查看>>
Android 自定义View (三) 圆环交替 等待效果
查看>>
设置虚拟机虚拟机中fedora上网配置-bridge连接方式(图解)
查看>>
HEVC播放器出炉,迅雷看看支持H.265
查看>>
[置顶] Android仿人人客户端(v5.7.1)——人人授权访问界面
查看>>
Eclipse 调试的时候Tomcat报错启动不了
查看>>
【安卓5】高级控件——拖动条SeekBar
查看>>
ES6内置方法find 和 filter的区别在哪
查看>>
Android入门之文件系统操作(二)文件操作相关指令
查看>>