博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《JavaScript高级程序设计 第三版》学习笔记 (六) 函数与闭包详解
阅读量:4157 次
发布时间:2019-05-26

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

一、函数声明

1.函数的声明语法:function funcName(){}。js执行前会把函数声明载入,因此可以在调用之后声明函数,不必向c语言一样先声明,再调用,然后写实现。这个学名叫“函数声明提升”。
2.函数的表达式语法:var funcName=function(){}。这种方式其实是先创建一个匿名函数,然后把函数的地址传给变量。所以在调用funcName之前,必须先写好函数,不能像声明语法那样先用,后声明。

二、作用域链的创建过程

[javascript]   
  1. //小实验  
  2. function compare(value1,value2){  
  3.     if(value1<value2){  
  4.         return -1;  
  5.     }else if(value1>value2){  
  6.         return 1;  
  7.     }else{  
  8.         return 0;  
  9.     }  
  10. }  
  11. var result=compare(3,4);  

1.函数的执行

(1)当函数compare第一次执行时,解析器会为它创建一个执行环境,完成其作用域链。
(2)使用this、arguments和其他命名参数的值初始化函数的活动对象。
(3)在作用域链中,第一层外部函数的活动对象处于第二位,全局执行环境里的活动对象在最后。
(4)函数执行过程中,为读取和写入变量,需要在作用域链中查找变量。

2.作用域链的生成

(1)创建compare时,预先创建一个包含全局变量的作用域链,这时作用域链中只有一个活动对象,活动对象里包含了this(指window)、compare、result。
(2)调用compare时,再创建一个活动对象,把该活动对象放在作用域链的顶端,之前包含全局变量的活动对象挪到第二。新的活动对象包含了value1和value2。
(3)compare执行完毕,(2)中创建的活动对象(包含value1和value2)会被销毁,compare的作用域链只剩下全局变量活动对象。

三、闭包

1.闭包的概念

(1)指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
(2)下面例子中return的匿名函数A就是一个闭包,因为A使用了另一个函数compare的局部变量attrName。
[javascript]   
  1. //小实验  
  2. //这是一个可以比较对象属性的函数,用于array.sort十分方便。  
  3. function compare(attrName){  
  4.     return function(object1,object2){
    //匿名函数A  
  5.         if(object1[attrName]<object2[attrName]){  
  6.             return -1;  
  7.         }else if(object1[attrName]>object2[attrName]){  
  8.             return 1;  
  9.         }else{  
  10.             return 0;  
  11.         }  
  12.     }  
  13. }  
  14. var func=compare("age");  
  15. var result=func({age:14},{age:15});  
  16. alert(result);//-1  
(3)虽然上面例子中的匿名函数A是返回到compare外面执行的,但是A的作用域链中包含了compare的局部变量。

2.闭包形成的原因

(1)正因为匿名函数A在compare内部,A的作用域链中才会包含compare创建的活动对象,从而可以使用compare的局部变量。如果还不清楚,请仔细阅读“作用域链的生成”,然后阅读下面的内容。
(2)匿名函数A从compare中返回后,它的作用域链内有两个活动对象:第一个是compare的活动对象(包含attrName);第二个是全局活动对象(包含compare、func、result),这里this先不说。
(3)更关键的问题(闭包能够工作的核心条件)是,compare执行完,返回匿名函数A给func后,它自己创建的活动对象(包含attrName)并没有销毁。因为func的作用域链中还在引用这个活动对象。这是js内存回收机制决定的,确切地说,compare执行完毕后,它的执行环境、作用域链会被销毁。但是作用域链其实只是一个队列,其中存放着指向活动对象的指针。活动对象被销毁的前提是所有指向该对象的指针都已经销毁了。因此,当func的作用域链包含指向compare活动对象的指针时,compare的活动对象仍要在内存中保留。
(4)当func执行完毕,func创建的活动对象(包含age14和age15)销毁,然后compare的活动变量才会销毁。

3.闭包的优缺点

(1)使用起来非常灵活。
(2)不容易理解,尤其是层次比较多的时候。
(3)闭包比普通函数占用内存多。因此不在万不得已的时候,不要使用闭包。即便使用闭包,层次也不要太多。

四、闭包与父级变量

1.最后的值

(1)闭包的作用域链中,保存着各个层次的活动对象,通过这些对象去使用外部函数的变量。这是闭包实现的基本原理。
(2)根据基本原则,外部函数执行完后,外部函数的活动对象中保存着局部变量最后的值。
[javascript]   
  1. //小实验  
  2. function box(){  
  3.     var arr=[];  
  4.     for(var n=0;n<10;n++){  
  5.         arr.push(function(){  
  6.             alert(n);     
  7.         });   
  8.     }  
  9.     return arr;  
  10. }  
  11. var funcs=box();  
  12. funcs[0]();//10  
  13. funcs[9]();//10  
(3)上面例子中,alert的永远是10。虽然box执行完毕后返回了一个闭包数组,而且每个闭包都能使用n,但n的值并不是想象中的0到9,而是10。原因在前面已经解释过了。为了让每个闭包alert出自己在数组中的索引,我们可以创建另一个匿名函数,并强制执行。

[javascript]   
  1. //小实验  
  2. function box(){  
  3.     var arr=[];  
  4.     for(var n=0;n<10;n++){  
  5.         arr[n]=function(num){  
  6.             return function(){  
  7.                 alert(num);   
  8.             }  
  9.         }(n);  
  10.     }  
  11.     return arr;  
  12. }  
  13. var funcs=box();  
  14. funcs[0]();//0  
  15. funcs[9]();//9  
(4)函数的强制执行
(补充知识)
:下面例子都是函数强制执行(以下是在v8引擎下实验的结果)。

  <1>如果不用变量保存强制执行的返回值,函数体必须用括号括起来(正常1)。否则如果函数是声明函数,不会强制执行(异常1);如果是匿名函数,会出现语法错误(异常2)。

  <2>如果使用变量保存强制执行的返回值,函数是不是匿名的,函数体括与不括,都没有问题(正常2-5)。

  <3>有很多人把“正常1”理解为闭包,其实是不正确的。这只是匿名函数强制执行而已,与闭包没有多大关系,只不过在闭包设计中,为了避免上面的问题,经常使用这种强制执行而已。

[javascript]   
  1. //小实验  
  2. //正常1  
  3. (function(i,j){  
  4.     alert(i+j);//3  
  5. })(1,2);  
  6. //异常1  
  7. function abc(i,j){  
  8.     alert(i+j);//没有alert,也就是没有执行。  
  9. }(1,2);  
  10. //异常2  
  11. function(i,j){  
  12.     alert(i+j);   
  13. }(1,2);//Unexpected token (  
  14.   
  15.   
  16. //正常2  
  17. var b=(function(i,j){  
  18.     alert(i-j);//-1  
  19.     return i-j;   
  20. })(1,2);  
  21. alert(b);//-1  
  22. //正常3  
  23. var a=function(i,j){  
  24.     alert(i+j);//3    
  25. }(1,2);  
  26. alert(a);//undefined  
  27.   
  28.   
  29. //正常4  
  30. var c=(function func(i,j){  
  31.     alert(i-j);//-1  
  32.     return i-j;   
  33. })(1,2);  
  34. alert(c);//-1  
  35. 正常5  
  36. var d=function func(i,j){  
  37.     alert(i-j);//-1  
  38.     return i-j;   
  39. }(1,2);  
  40. alert(d);//-1  

五、闭包与this指针

1.this指针是函数运行时基于函数的执行环境绑定的。换句话说,this跟作用域链没啥直接关系,看的是函数在哪儿。
2.闭包在哪儿执行谁也不好说,this指的到底是啥,也要看运行期。所以在闭包中使用this要格外小心。(这种题目在面试中很多,我在A公司2015校招web前端线试题中就遇到一个。)
[javascript]   
  1. //小实验  
  2. var name="window";  
  3. var obj={  
  4.     name:"obj",  
  5.     getName:function(){  
  6.         return function(){  
  7.             return this.name;     
  8.         }  
  9.     },  
  10.     getName1:function(){  
  11.         var _this=this;  
  12.         return function(){  
  13.             return _this.name;    
  14.         }  
  15.     }  
  16. }  
  17. alert(obj.getName()());//window  
  18. alert(obj.getName1()());//obj  

转载地址:http://vwkxi.baihongyu.com/

你可能感兴趣的文章
idle进程创建
查看>>
内核线程创建
查看>>
linux elf tool readelf
查看>>
linux tool objdump
查看>>
linux tool nm
查看>>
字节对齐
查看>>
把类成员函数封装成线程API所需要的函数
查看>>
HTTP Live Streaming直播(iOS直播)技术分析与实现
查看>>
Ribbon界面图标可以直接用PNG做透明图标
查看>>
向其他软件窗口、控件发送消息的方法
查看>>
word或者pdf文件全部保存为图片的方法
查看>>
VS2010下SQLite3生成lib库文件
查看>>
sqlite3的helloworld
查看>>
MFC下支持中文的SQLite3封装类使用
查看>>
简单高效的多线程日志类
查看>>
研华USB4711A采集卡高速中断模式采集总结
查看>>
从零起步CMFCToolBar用法详解
查看>>
CMFCRibbonStatusBar用法
查看>>
CMFCControlRendererInfo类的参数
查看>>
史上最详细MFC调用mapX5.02.26步骤(附地图测试GST文件)
查看>>