资讯专栏INFORMATION COLUMN

一篇文章带你理解闭包

bluesky / 1378人阅读

摘要:通过例子再来回顾一下闭包的理解所谓的闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。此时就是一个闭包,这样写有些麻烦,我们对此改进一下。参考文章初识中的闭包从闭

走在前端的大道上

本篇将自己读过的相关 javascript闭包 文章中,对自己有启发的章节片段总结在这(会对原文进行删改),会不断丰富提炼总结更新。

首先着重回顾一下 全局变量 和 局部变量 全局变量:可以在任意位置访问的量就叫 全局变量

 

var age = 20;
 function a(){
     console.log(age); //20
}
a();
局部变量:函数中用var定义的变量,只能在函数中访问这个变量,函数外部访问不了。
function a(){
    var age = 20;
}
a();
console.log(age); // Uncaught ReferenceError: age is not defined

重点:

1.在函数中如果不使用var定义变量那么js引擎会自动添加成全局变量。
2.全局变量从创建的那一刻起就会一直保存在内存中,除非你关闭这个页面,局部变量当函数运行完以后就会销毁这个变量,假如有多次调用这个函数它下一次调用的时候又会重新创建那个变量,既运行完就销毁,回到最初的状态,简单来说局部变量是一次性的,用完就扔,下次要我再重新创建。
函数的相关知识点:

1.一个函数内可以嵌套多个函数
2.函数里面的子函数可以访问它上级定义的变量,注意不只是一级,如果上级没有会继续往上级找,直到找到为止,如果找到全局变量到找不到就会报错。

function a(){
 var name = "追梦子";
 function b(){
     console.log(name); // "追梦子"
 }
 b();
}
a();

3.函数的另外一种调用形式,你可以把它叫做自调用,自己调用自己,达到自执行的效果。

var a = 0;
(function(){
   console.log(++a); // 1
})()

这种方式用()把内容包裹起来,后面的()表示执行这个函数,可能你会问为什么要把函数包起来,如果不包裹起来,js会把它当作函数声明来处理,如果包裹起来就是表达式。

正题
闭包说得通熟易懂一点,就是指 有权访问另一个函数作用域变量的函数。闭包可以解决函数外部无法访问函数内部变量的问题。创建闭包的常见方式,就是在一个函数内部创建另外一个函数,并返回。

下面是一段没有使用闭包的代码:

function fn(){
 var a = 10; //报错
}
alert(a);

因为a没有定义,虽然函数fn里面定义了a但是,但是它只能在函数fn中使用。也就是作用域的问题。

function fn(){
  //定义了一个变量name
 var name = "追梦子";
  //外部想访问这个变量name怎么办?return!把它返回出去,再用个变量接收一下不就可以了
  return name;
}
var name = fn();//接收fn返回的name值。
alert(name);//追梦子;

这里的闭包就是利用函数的return。除了通过return还可以通过其他的几种方法如下:
方法1:

function fn(){
  var a = 0;
  b = a;
}
alert(b)

 这里利用了js的一个特性,如果在函数中没有用var定义变量,那么这个变量属于全局的,但这种方法多少有些不好。

方法2:

var f = null;
function fn(){
  var a = 0;
  f = function(){
    a++;
    f.a = a;
  };
}
fn();
f();
alert(f.a);//1
f();
alert(f.a);//2

其实闭包还有一个很重要的特性,来看一个例子。

var lis= document.getElementsByTagName["li"]; 
 //假如这段代码中的lis.length = 5;
for(var i=0;i

最终结果是不管单击哪个li元素都是弹5。不信你试试。为什么呢。看下面分析。

for(var i=0;i

为什么会这样呢,因为你for循环只是给li绑定事件,但是里面的函数代码并不会执行啊,这个执行是在你点击的时候才执行的好吧?但是此时的i已经是5了,所以所有的都打印出5来了。

for(var i=0;i

闭包的特点不只是让函数外部访问函数内部变量这么简单,还有一个大的特点就是 通过闭包可以让函数中的变量持久保持

function fn(){
   var num = 5;
   num+=1;
   alert(num);
}  
 fn(); //6
 fn(); //6

为什么都是 6 呢?因为 函数一旦调用里面的内容就会被销毁,下一次调用又是一个新的函数,和上一个调用的不相关了。

划重点:JavaScript中有回收机制,函数没有被引用 且执行完以后这个函数的作用域就会被销毁,如果一个函数被其他变量引用,这个函数的作用域将不会被销毁,(简单来说就是函数里面的变量会被保存下来,你可以理解成全局变量。)

再来

function fn(){
 var num = 0;
 return function(){
   num+=1;
     alert(num);   
   };  
}
var f = fn();
f(); //1
f(); //2

定义了一个fn函数,里面有个num默认为0,接着返回了一个匿名函数(也就是没有名字的函数)。我们在外部用 f 接收这个返回的函数。这个匿名函数干的事情就是把 num 加 1,还有我们用来调试的 alert 。

这里之所以执行完这个函数 num 没有被销毁,是因为那个匿名函数的问题,因为这个匿名函数用到了这个 num,所以没有被销毁,一直保持在内存中,因此我们 f() 时 num 可以一直加。

function a(){
    var aa = 0;
    function b(){
        aa ++;
        console.log(aa);
    }
    return b;
}
var ab = a();
ab(); //1
ab(); //2

里面的变量的值没有被销毁,因为函数a被外部的变量ab引用,所以变量aa没有被回收。

如果某个函数被它的父函数之外的一个变量引用,就形成了一个闭包

还有一种更为常用的闭包写法

var bi = (function(){
    var a = 0;
    function b(){
        a ++;
        console.log(a);
    }
    return b;
})();

bi(); //1
bi(); //2
bi(); //3

执行过程分析:

  首先把一个自执行函数赋值给了bi,这个自执行函数运行完成以后就bi的值就变成了

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

  因为我们在上面的代码 return 回去了 b,然后因为这个自执行函数被 bi 引用所以里面的变量 a 并没有因为这个自执行函数执完而销毁,而是保存到了内存中,所以我们多次打印 bi()
就成了1、2、3

闭包是使用可以带来以下好处:

希望一个变量长期驻扎在内存中

避免全局变量的污染

私有成员的存在

闭包可以读取到函数内部的变量,这是由于闭包后函数的堆栈不会释放,也就是说这些值始终保持在内存中。这是一个优点,也是一个缺点。

通过例子再来回顾一下

闭包的理解:
  所谓的闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。
闭包的创建:
  一个函数中嵌套另外一个函数,并且将这个函数return出去,然后将这个return出来的函数保存到了一个变量中,那么就创建了一个闭包。
var arr = [];
for(var i=0;i<2;i++){
    arr[i] = function(){
        console.log(i);
    }
}
arr[0](); //2
arr[1](); //2

  实际情况我们是要打印0,1,2,3这样的数,但是每次都是打印2,什么情况呢?虽然我们在for中给arr的每个值添加了一个匿名函数,但是 在for循环中我们并没有执行这个函数,而是在for循环以后执行的这个函数,那么自然打印出来的就是for循环完以后i的值。

var arr = [];
// for(var i=0;i<2;i++){
    var i = 2;
    arr[0] = function(){
        console.log(i);
    }
    arr[1] = function(){
        console.log(i);
    }
// }
arr[0](); //2
arr[1](); //2

  相当于这样,虽然这个函数没有执行,但是arr的i已经给执行了,因为arr不是函数啊,肯定是执行的,而你函数没有调用自然就不会执行,当函数调用的时候i已经是2了。既然如此只要我们在for循环的时候直接执行这个函数就ok。

var arr = [];
for(var i=0;i<2;i++){
    arr[i] = function(){
        console.log(i);
    }
    arr[i]();
}
//0
//1

 这样,在每一次for循环的时候就直接执行了这个函数,打印正常,但是我们这样同样有一个问题,那就是在每一次for循环的时候这个函数就已经被执行了,我们要的是我们想什么时候调用就时候调用,而不是直接在for执行中直接执行,那么显然这样做是达不到我们的目的的。

  现在我们在想想闭包的概念,闭包可以创建独立的环境,并且每一个闭包的环境是独立的,也就是说,我们可以通过闭包来保存这些不同的变量。

我们回顾一下闭包的创建方法:一个函数中嵌套另外一个函数,并且将这个函数return出去,然后将这个return出来的函数保存到了一个变量中,那么就创建了一个闭包

var arr = [];
for(var i=0;i<2;i++){
    arr[i] = a(i);
}

function a(i){
    return function(){
        console.log(i);
    }
}

arr[0](); //0
arr[1](); //1

   此时就是一个闭包,这样写有些麻烦,我们对此改进一下。

var arr = [];
for(var i=0;i<3;i++){
    arr[i] = (function(i){
        return function(){
            console.log(i);
        }
    })(i)
}

arr[0](); //0
arr[1](); //1
arr[2](); //2

还可以这样

var arr = [];
for(var i=0;i<3;i++){
    (function(i){
        arr[i] = function(){
            console.log(i);
        }
    })(i)
}

arr[0](); //0
arr[1](); //1
arr[2](); //2

 此时 arr 里面的 i 用的是闭包里面的 i,而不是 for 中的 i,因为我们说过每个闭包的环境都是独立的。

js中的回收机制
function a(){
  var num = 10;
  return function(){
    num ++;
    console.log(num);
  }
}
a()(); //11
a()(); //11

按理说第二次执行函数a的时候应该打印出12才对,但是打印的却是11。

首先来看看我们的理解

1.我们在函数a中返回了一个匿名函数,在这个匿名函数中我们num++了一下,然后我们在函数外面执行了这个匿名函数函数,(第一括号执行函数a第二个括号执行这个rutrun回去的函数)
2.现在num是11,然后我们又执行了一次这个函数,你们应该是12吧,为什么不是呢?

实际js的执行

  但是js的设计者为了让没有必要的变量保存在内存中,(我们写的任何变量都是需要内存空间的),什么叫没有必要的变量?也就是说你不在需要这个变量的时候它就会被销毁?那么你肯定会问js怎么知道那些变量是我们不需要的哪些是我们需要的。所以js为了知道哪些变量需要保存下来,哪些不需要保存下来,会进行一些判断。接下来我们就一起看看js是怎么判断的。

1.在js中定义的全局变量是不会被销毁的,因为我们随时都可能会用到这个变量,所以不能被销毁。

2.但是在函数中定义的变量就不一定了,而且由于在函数的定义的变量的生命周期在执行完这个函数就销毁的原因自然就保存不了上一次的值。

3.但是并不是说函数就真的保存不了上一次的值,因为有的时候我们确实需要上一次的值,所以js判断是否需要保存上一次变量的值的时候就会遵守这样一个规则:

如果这个函数有被外部的变量引用就不会销毁(这句话说的不够准确,下面代码会一步一步解释),否则销毁。怎么理解这句话呢?

function a(){
    var b = 0;
    return function(){
        b ++;
        console.log(b);
    }
}

var d = a();
d();//1
d();//2

  函数a被变量d引用,更准确的说是函数a里面的那个匿名函数被变量d所引用,因为变量d等于的是函数a执行完成后的值,而函数a执行完以后又因为函数a返回了那个匿名函数,所以准确的说是变量d等于匿名函数。而这个匿名函数因为使用了函数a中的变量b并且还被变量d所引用,所以就形成了一个闭包,只要这个变量d不等于null的话,那么那个变量b会一直保存到变量d中不会被销毁。

总结:

1、如果一个对象不被引用,那么这个对象就会被GC回收;
2、如果两个对象互相引用,但是没有被第3个对象所引用,那么这两个互相引用的对象也会被回收。

参考文章:
1.初识js中的闭包
2.从闭包案例中学习闭包的作用,会不会由你
3.那些年我们一起过的JS闭包,作用域,this,让我们一起划上完美的句号
4.再次讲解js中的回收机制是怎么一回事。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92071.html

相关文章

  • 【进阶1-4期】JavaScript深入之带你走进内存机制

    摘要:引擎对堆内存中的对象进行分代管理新生代存活周期较短的对象,如临时变量字符串等。内存泄漏对于持续运行的服务进程,必须及时释放不再用到的内存。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第一期,本周的主题是调用堆栈,今天是第4天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计划...

    不知名网友 评论0 收藏0
  • 段代码,带你理解js执行上下文的工作流程

    摘要:由于函数被调用了,线程会从刚刚保存的变量中取出内容,去解析执行它。最后,当线程遇到离开上下文的标识,便离开上下文,并把的结果一并返回出去。 原文链接,欢迎关注我的博客 我相信很多前端初学者一开始都会被执行上下文这个概念弄晕,或者说似懂非懂。对于工作两年的我来说,说来实在惭愧,虽然知道它大概是什么,但总觉得没有一个更为清晰的认识(无法把它的工作过程描述清楚),因此最近特意温习了一遍,写下...

    developerworks 评论0 收藏0
  • 【进阶2-2期】JavaScript深入之从作用域链理解闭包

    摘要:使用上一篇文章的例子来说明下自由变量进阶期深入浅出图解作用域链和闭包访问外部的今天是今天是其中既不是参数,也不是局部变量,所以是自由变量。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第二期,本周的主题是作用域闭包,今天是第7天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计...

    simpleapples 评论0 收藏0
  • 带你了解什么是JavaScript 函数式编程?

    摘要:前言函数式编程在前端已经成为了一个非常热门的话题。整个过程就是体现了函数式编程的核心思想通过函数对数据进行转换。高阶函数函数式编程倾向于复用一组通用的函数功能来处理数据,它通过使用高阶函数来实现。 前言 函数式编程在前端已经成为了一个非常热门的话题。在最近几年里,我们看到非常多的应用程序代码库里大量使用着函数式编程思想。 本文将略去那些晦涩难懂的概念介绍,重点展示在 JavaScrip...

    acrazing 评论0 收藏0
  • 【进阶2-3期】JavaScript深入之闭包面试题解

    摘要:闭包面试题解由于作用域链机制的影响,闭包只能取得内部函数的最后一个值,这引起的一个副作用就是如果内部函数在一个循环中,那么变量的值始终为最后一个值。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第二期,本周的主题是作用域闭包,今天是第8天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了...

    alanoddsoff 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<