闭包之自执行函数

来源:互联网 发布:淘宝怎么设置快递费 编辑:程序博客网 时间:2024/06/01 08:32

         不知道你对上篇文章的闭包理解多少,其实也不必强求自己一下就是理解了,因为这是中间会有一个思维的转换过程,按照这样的学习思路。今天我们再来看一个经典的闭包案例。

        相信使用过Jquery 的同学对自执行函数都不陌生,因为你可能已经大量的使用过了,因为他的好处也是被人说烂的。比如说别人问你为什么要是使用自执行函数,你会不假思索的说可以防止变量污染,按照套路来的话,别人会接着问你为什么,你会说因为他是闭包啊!!!

        现在你再来仔细体会一下这段话,会不会觉得从始至终你都没有说清楚为什么会防止变量污染,你说是因为闭包,如果你看过上一节的闭包概念,你肯定觉得这个回答有问题,因为闭包只是取得了外部作用域的引用,而不是防止变量被修改,是不是这样子,好了,说到这,我们还是拿出程序来分析一下:

(function (){     console.log(543535335)})()


这是不是就是你熟悉的自执行函数,他会在程序运行到他时就打印你期待的值,但是我写的这个算是闭包吗?不用急着回答是或者不是,先来想一下,我们说,闭包是函数记住并访问他的外部作用域,也就是取得外部作用域的引用,这里你仔细看一下,里面的函数并没有使用外部作用域的任何变量,也就不存在取得作用域的引用,所以这里不是闭包,是不是说到这你觉得你的三观尽毁的感觉,没关系,回了我们重新建。在看个例子,这个会彻底摧毁你的三观:

(function (){console.log(635333)}())


        是不是觉得我很2,这样子写代码,因为你会觉得这样子写是错的,因为你在学习 js 的时候,你看的书或者是你的老师基本不会告诉你这种反人类(先这么叫着)的写法,其实他是对的,他的作用跟我们写的第一段代码是等价的,你写哪个都可以,但我建议你还是保留你习惯的写法。
        好了,不管你是以那种方式写的他们都不是闭包,因为他们并没有使用外部作用域,只是将自己封闭起来,不让别人使用。那你可能会问,这算什么呢?其实他叫做仿快级作用域,对这里是仿,这句话我是在《js高级教程》里面抄的(原谅我的无耻),但我觉得这样子说是最恰当的,之所以是仿,是因为在 ES6 以前,js 是不存在快级作用域的,只能用这种方式去仿,那么他们是怎么实现仿呢?这个就是我们下面要巴拉的内容了。

         你一定记得你在学习 js 的时候有人告诉你,js 是存在变量提升的(希望你没有忘),就比如,你定义的变量和用 function 关键字声明的函数,在编译时都是可以提升到当前作用域的顶部的,这就允许你即使在定义他们之前就可以使用他们(有时可能得到的结果不能达到你的预期),但是你注意到没有,我前面说的是用 function 关键字声明的函数,其实我说这句就是废话,不用这个就无法声明函数,其实我想表达的是,如果你想声明一个函数 function 关键字一定是你这一行的第一个单词。也只有这种方式声明的函数才会在编译时得到提升,你有没有感觉了,是不是前面的两个函数都是括号开头,如果你注意到了,恭喜你即将学会这个只是点了,我这里准备使用代码展示:

// 这是函数声明function aa(){}// 这是函数表达式(function aa(){})()

         现在你就应该清楚了,只有函数声明才会被提升,而函数表达式是得不到提升的,所以就会形成一个自我封闭的作用域,外部是无法进行访问的,其实在这种理解上,还有几种写法也是有这个效果的:

// 函数表达式var aa = function bb(){}function cc(){     function dd(){     }}


        看清楚,第一个叫函数表达式,第二个就是普通的函数声明,他时会提升的。但是他们的效果是一致的,你无法在全局作用域中访问 bb 和 dd。这样是不是在 bb 和 dd 中,同样会形成一个仿快级作用域,只是这两种写法比起自执行函数来相对复杂,而且我们仅仅是想要一个快级作用域,所以自执行函数就是首选啦。


        看到这,相信你已经知道你认为前面写的自执行函数是闭包错在哪了,因为他在执行时没有引用外部作用域,仅此而已。

        下面就来看看他作为闭包的例子,这个例子也是相当经典,那就是 for 循环:

         

for(var i=0;i<5;i++){    (function (j){         console.log(j)    })(i)}

       相信你想要保存每个 i 是就是这样子做的,这样的自执行函数就是闭包,因为他取得了声明在全局作用域的 i 的引用,也就是取得了全局作用域的引用(原谅我净在这说废话)。虽然听起来我说的好像很不靠谱,但是实际情况就是这样子的,其实这个对闭包理解不是很深入的人来说理解起来会有点困难,因为我们仅仅是将这个表象给你展示出来并没有像你展示其中的原理,要搞定这个的原理,我们要搞懂一件事,就是,闭也就是说,当我们将一个值作为参数传递的时候,他时以哪种方式传递的,这个答案相信你也很清楚,js 是按值传递的,也就是说,当你把一个变量当做变量传递给函数时,函数是赋值这个值的当前状态的,不会随着值的改变而改变,这也就是说,当我们在调用函数时,我们将一个变量作为参数传递给函数,函数会自动的将这个变量复制一份,而不是仅仅取得这个变量的引用。这个问题搞懂以后,上面的这个循环执行的过程就很清晰了。

       首先你要明白,循环5 次相当于我们我们创建了5个自执行函数,当然,这里我们还隐式的创建了一个全局变量 i ,他得值是随着循环的进行逐渐增加的,最后的值是 5,所以,当 i == 0 时,自执行函数会接受到此时 i 的值,也就是 0 ,这时console.log 这个函数取得了外部函数的参数 j 的引用,将其值打印出来,这就是这个函数配合循环执行的过程,现在再来看一下,闭包究竟在哪产生的,首先是我们将 i 当做参数传递给了自执行函数,这里会有点难以理解,因为这里的实际操作过程要比代码本身看起来复杂的多,所以还是看下面明显的例子,其实他们是等价的:

function aa(){ // 接收一个函数作为参数    return function(a){
        console.log(a)
    }}for(var j=0;j<5;j++){    aa()(j)}

  这个例子的运行结果跟上面的自执行函数运行的结果是一致的,因为他们的本质是一致的,你仔细看看把 aa 变成一个匿名函数,然后再将一个匿名函数当做参数传给他,这时你就会看到下面的代码:

// 首先把aa变成匿名函数for (var j=0;j<5;j++){      ()(j); // --->这里我们将 aa 变成一个匿名函数}// 然后把一个匿名函数当做参数传递给他for (var j=0;j<5;j++){      (function(a){
          console.log(a)
      })(j); // --->这里我们将一个匿名函数当做参数传递给这个替换的匿名函数}


  看到这,是不是你就熟悉起来了呢?其实他就是这么来的,我觉得可能是一代代程序员总结简化出来的写法,现在你就很清楚这个闭包的执行过程,如果你还是不很理解,可以自己在去理解一下 上面 aa 函数这个经典闭包的执行过程,其实 aa 函数那样子写,你可能会问不一样啊,aa 函数里面是定义好的函数,执行就好了,没问题,我把 aa 函数给改造一下:

function aa(fun){ // aa函数接收一个函数作为参数,并且将函数作为返回值给返回出去    return fun;}for(var j=0;j<5;j++){    aa(function(a){ //aa 执行后的结果是传进去的匿名函数,函数()便是执行函数        console.log(a);    })(j)}


现在你是不是又有了新的理解呢?只要这样子一看,你就清晰的知道了自执行函数的原理是什么,所以,这个地方的闭包实际上是在穿进去的匿名函数中出现的,因为 aa 中不存在变量,但是匿名函数是作为 aa 的参数传递进来的,所以他的返回值就是引用了这个参数获得的,所以这里是一闭包,接下来就是传进去的匿名函数,这里的闭包是由于匿名函数 console.log 引用了参数 a 而形成的,所以这里又形成了一个闭包。

   说了这么多,在最后我们来总结一下我们学到了什么。

   首先我们学到了什么是仿快级作用域,如果你不给自执行函数传递参数,他就是一个仿快级作用域,因为你不传递参数,匿名函数内部函数就不会引用这个匿名函数的作用域中的变量(仅仅值前面写的函数),也就不存在闭包,所以只是形成了一个封闭的快级作用域。

   然后我们知道了函数表达式和函数声明的区别,这里在提一下,只要 function 不是函数声明这一行的第一个单词(或者其前面有其他占位符)这时的函数声明就是函数表达式。

   接下来我们分析了自执行函数的演化过程,这样你就很清晰的知道他是怎样形成的闭包。

   最后,还是要啰嗦一下,闭包只有在函数身上才会出现,还有就是函数要使用外部作用的东西。如果内部函数仅仅是定义在一个函数内部,他们没有一点变量交流,着一定不会是闭包。好了,这次就巴拉到这了,有错误和不足之处希望各位指出,谢谢。






                                             
0 0
原创粉丝点击