• 博客园logo
  • 会员
  • 周边
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
小许学习笔记
博客园    首页    新随笔    联系   管理    订阅  订阅
JavaScript 【函数表达式】闭包

以下大部分为学习《JavaScript 高级程序设计》(第 3 版) 所做笔记。

目录:

1、了解闭包

2、闭包与变量

3、关于 this 对象

4、闭包用途

5、闭包使用场景

  

一、了解闭包

  由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存。过度使用闭包可能会导致内存占用过多。

  闭包不等同于匿名函数。闭包是指有权访问另一个函数作用域中的变量的函数。

  创建闭包的常见方式是在一个函数内部创建另一个函数。

  要理解闭包需要理解作用域链的概念,这里有作用域链相关笔记:https://www.cnblogs.com/xiaoxuStudy/p/12535960.html

  一般来讲,当函数执行完毕后,局部对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是,闭包的情况有所不同。在另一个函数内部定义的函数会被包含函数(即外部函数)的活动对象添加到它的作用域链中。

 1 <script>
 2     function createComparisonFn( propertyName ){
 3         return function( obj1, obj2 ){
 4             var value1 = obj1[propertyName];
 5             var value2 = obj2[propertyName];
 6             if( value1 < value2 ){
 7                 return -1;
 8             }else if( value1 > value2 ){
 9                 return 1;
10             }else{
11                 return 0;
12             }
13         };
14     }
15     var compareNames = createComparisonFn("name");  //创建函数
16     var result = compareNames( { name:"mo" }, { name:"na" } );  //调用函数
17     console.log(result);    //输出:-1
18 </script>

  在 createComparisonFn() 函数内部定义的匿名函数的作用域链中,实际将会包含外部函数 createComparisonFn() 的活动对象。在匿名函数从 createComparisonFn() 中被返回之后,它的作用域链被初始化为包含 createComparisonFn() 函数的活动对象和全局变量对象。这样匿名函数就可以访问在 createComparisonFn() 中定义的所有变量。更为重要的是,createComparisonFn() 函数在执行完毕之后,其活动对象也不会销毁,因为匿名函数的作用域链仍然在引用这个活动对象,换句话说,当 createComparisonFn() 函数返回之后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中。直到匿名函数被销毁后,createComparisonFn() 的活动对象才会被销毁。

  调用 compareNames() 过程中产生的作用域链之间的关系:

怎么销毁匿名函数呢?

  创建函数保存在变量中,通过将变量设置为 null 解除函数的引用,就等于通知垃圾回收例程将其清除。随着匿名函数的作用域链被销毁,其他作用域(除了全局作用域)也都可以安全地销毁了。

1     //创建函数
2     var compareNames = createComparisonFn("name");  
3     //调用函数
4     var result = compareNames( { name:"mo" }, { name:"na" } );
5     //解除对匿名函数地引用(以便释放内存)
6     compareNames = null;

 

二、闭包与变量

  作用域链的配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。

 1 <script>
 2     function fn(){
 3         var result = new Array();
 4         //给数组的前10项各自创建一个闭包
 5         for(var i=0; i<10; i++){
 6             result[i] = function(){
 7               return i;
 8             };
 9         }
10         //循环完毕,此时 i=10
11         return result;
12     }
13     var fn1 = fn();
14     for( var j=0; j<fn1.length; j++ ){
15         console.log( fn1[j]() );    //输出了10个10
16     }
17 </script>

 

  表面上看,数组 result 的值应该是[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] ,实际上是[ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]。

  fn() 函数要返回时(即执行  return result;语句时),搜索过程从作用域链的前端开始,变量 result 是 fn() 中的局部变量,搜索到了定义 result 的地方,搜索停止,变量 result 保存的是一个数组,而在 fn() 返回之前已经用 for 语句将闭包赋值给数组的每一项且循环结束后 i = 10,每个函数都引用着变量 i, 变量 i 是 fn() 的局部变量,所以 fn() 返回后数组的每项函数内部 i 的值都是10。

变量 i 是 fn() 函数的活动对象 。当 fn() 函数返回后,变量 i 的值是 10,此时每个函数都引用着保存变量 i 的同一个变量对象,所以在每个函数内部 i 的值都是10。

 

 那么怎么让闭包的行为符合预期呢?

  可以创建另一个匿名函数强制让闭包的行为符合预期。不直接把闭包赋值给数组,而是定义一个匿名函数,并将立即执行该匿名函数的结果赋值给数组。这里匿名函数有一个参数 num ,也就是最终函数要返回的值,在调用每个匿名函数时传入变量 i , 因为函数参数是按值传递的,所以就会将变量 i 的当前值复制给参数 num。而在这个匿名函数内部,又创建了一个访问 num 的闭包。这样一来,result 数组中的每个函数都有自己 num 变量的一个副本。

(立即执行函数相关的笔记:https://www.cnblogs.com/xiaoxuStudy/p/12354095.html#IIFE  函数参数按值传递相关的笔记:https://www.cnblogs.com/xiaoxuStudy/p/12509729.html#three)

 1 <script>
 2     function fn(){
 3         var result = new Array();
 4         for(var i=0; i<10; i++){
 5             result[i] = function( num ){
 6                 return function(){
 7                     return num;   
 8                 }
 9             }( i );
10         }
11         return result;
12     }
13     var fn1 = fn();
14     for( var j=0; j<fn1.length; j++ ){  //依次输出数组 result 的元素
15         console.log( fn1[j]() );
16     }
17 /*输出:
18 0
19 1
20 2
21 3
22 4
23 5
24 6
25 7
26 8
27 9
28 */
29 </script>

  

三、关于this 对象

   this 对象是在运行时基于函数的执行环境绑定的:在全局函数中,this 等于 window, 而当函数被作为某个对象的方法调用时, this 等于那个对象。

  不过,匿名函数的执行具有全局性,因此其 this 对象通常指向 window。但有时候由于编写闭包的方式不同这一点可能不会那么明显。

 1 <script>
 2     var name = "the window";
 3     var obj = {
 4         name : "the object",
 5         //闭包
 6         //函数作为某个对象的方法,返回一个匿名函数
 7         sayName : function(){
 8             return function(){
 9                 return this.name;
10             }
11         },
12         //函数作为某个对象的方法
13         sayName2 : function(){
14             return this.name;
15         }
16     }
17     //闭包
18     console.log( obj.sayName()() );     //输出:the window
19     //函数作为某个对象的方法被调用
20     console.log( obj.sayName2() );      //输出:the object
21 </script>

为什么匿名函数没有取得其包含作用域(或外部作用域)的 this 对象呢?

  每个函数在被调用时都会自动取得 2 个特殊变量:this 和 arguments 。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。

 

那么怎么能让闭包访问其包含作用域(或外部作用域)的 this 对象呢?

  把外部作用域中的 this 对象保存在一个闭包能够访问的变量中,就可以让闭包访问该对象了。

 1 <script>
 2     var name = "the window";
 3     var obj = {
 4         name : "the object",
 5         sayName : function(){
 6             //在定义匿名函数之前把 this 对象赋值给一个名叫 that 的变量
 7             var that = this;
 8             return function(){
 9                 return that.name;
10             }
11         }
12     }
13     console.log( obj.sayName()() );     //输出:the object
14 </script>

 

  即使是语法的细微改变,都有可能意外改变 this 的值。

 1 <script>
 2     var name = "the window";
 3     var obj = {
 4         name : "the object",
 5         sayName : function(){
 6             console.log( this.name );
 7         }
 8     }
 9     obj.sayName();      //输出:the object
10     (obj.sayName());    //输出: the object
11     (obj.sayName = obj.sayName )(); //输出:the window
12 </script>

  调用 obj.sayName(),返回的是:“the object”,因为 this.name 就是 object.name。

  后 2 种调用方式我看不懂,先记录下来:调用方法前加括号 ( obj.sayName )() ,虽然加上括号之后,就好像只是在引用一个函数,但 this 的值得到了维持,因为 obj.sayName 和 (obj.sayName) 的定义是相同的。(obj.sayName = obj.sayName )() 先执行了一条赋值语句,然后再调用赋值后的结果。因为这个赋值表达式的值是函数本身,所以 this 的值不能得到维持,结果就返回了 "the window"。

 

四、闭包用途

截图自网络资料:

 

五、闭包应用场景

setTimeout 的传递的第一个函数不能带参数,使用闭包可以实现传参效果。

不使用闭包,第一个函数带参数会报错:

function func(param){
    console.log(param);
}
var f1 = func('小许');
setTimeout(f1, 1000);    
//报错:Callback must be a function. Received undefined

使用闭包可以实现传参效果:

function func(param){
    return function(){
        console.log(param);
    }
}
var f1 = func('小许');
setTimeout(f1, 1000);    //输出:小许

 

posted on 2020-03-25 08:40  xiaoxustudy  阅读(270)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3