Blogger Information
Blog 82
fans 0
comment 1
visits 108243
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
闭包 随笔
子龙的博客搬家啦httpswwwcnblogscomZiLongZiLong
Original
1740 people have browsed it

开始正式介绍之前先看一个比较有难度的关于闭包的面试题:

function fun(n,o) {          
    console.log(o)            
    return {            
        fun:function(m){                 
            return fun(m,n);            
        }         
     };       
 }        
 var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);        
 var b = fun(0).fun(1).fun(2).fun(3);        
 var c = fun(0).fun(1);  c.fun(2);  c.fun(3);        //问:三行a,b,c的输出分别是什么?

    (原文链接:http://www.cnblogs.com/xxcanghai/p/4991870.html)  

这道题是个人拿到手都会感到棘手,尤其对于一些基础不扎实的新手,看着看着就看晕了,我大体解释一些这个fun函数干了一些什么事(文章最后再分析一下这个): 首先,打印了一下他的第二个参数,然后return了一个对象,这个对象有一个方法fun,然后这个方法return了fun(m,n)--最外层函数的执行结果;大体弄懂基本的流程,就可以大概分析一下了,不过讲闭包之前,还是讲一下js的作用域链:

(来自四年后的补充: 这个题看着有点难逐次递归也是一个方面,哈哈)  

每一段js代码(全局代码或函数)(注意一下这里,敲黑板!!!)都有一个与之关联的作用域链(scope chain)。这个作用域链是一个对象列表或者链表,这组对象这组对象定义了这段代码作用域中的变量,当js需要查找变量x的值得时候,(这个过程叫做‘变量解析(varaible resolution)’),他会从链中的第一个对象查找,如果在第一个对象中找不到就会查找下一个,以此类推。  在js顶层代码中,作用域链由一个全局对象组成。在不包含嵌套的函数体内,作用域链上有两个对象第一个是该函数定义参数和局部变量的对象,第二个是全局对象。                                                                

--以上两段的内容来自js权威指南,就是那本犀牛书 ,当定义一个函数时,它实际上保存一个作用域链。当调用这个函数时,它创建一个新的对象来存储他的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的链。对于嵌套函数来讲每次调用外部函数时,作用域链都是不同的,内部函数又会重新定义一遍,每次调用外部函数时,内部函数的代码都是相同的,而关联这段代码的作用域链也不相同。

  --以上内容也是来自js权威指南,就是那本犀牛书  大概概括一下就是每个函数会形成一条作用域链,作用域链不同,能访问到的内容也不尽相同,由于作用域链上对象的排序关系访问变量时,可以遵循一个原则--就是就近原则。    

说完作用域之后,就来谈谈闭包吧。  简言之,混乱的(2022.1.22的补充,此处混乱的用词可能并不准确)作用域链就是形成闭包的元凶。  来一个极为简单的例子:

var d;      
function outter(){          
    var c = 0;          
    console.log(c);          
    d = function(){ 
        c++; 
    }      
 }     
 outter();      
 d();//0        
 d();//1

 

为什么会这样呢? 一般而言,每次调用js函数时,都会创建一个新的对象用来保存局部变量,并把这个对象添加至作用域链中,当函数返回之后,就从作用域链中将这个对象删除,如果不存在嵌套函数也没有其他引用指向这个绑定对象,他就会被当做garbage回收。如果定义了嵌套函数,每个嵌套函数都会形成自己的作用域链,并且这个作用域链指向一个变量绑定对象,如果这些嵌套的函数对象在外部函数中保存下来,那么他们也会和所指向的变量绑定对象一样被当做garbage回收。但是如果将这些嵌套函数作为返回值返回,或存储在某处的属性里,就会有一个外部的引用指向这个嵌套的函数。它就不会被当做garbage回收,并且所指向的变量绑定对象,也不会被当做garbage回收!    

所以在上边的代码里当调用outter之后,由于外部变量d引用了嵌套的函数,故而在outter执行完毕之后,d指向的嵌套函数何其所指向的变量绑定对象是没有被回收的,所以c也没有回收,所以有了往后的故事。。。  来一个闭包的英文解释(以下摘自MDN的解释):        

A closure is the combination of a function and the lexical environment within which that    function was declared.  

 什么意思?上边话直接翻译过来就是:闭包是一个函数和这个函数被声明的词法作用域环境的组合。    再说一下开头说到的这道题:     

function fun(n,o) {          
    console.log(o)            
    return {            
        fun:function(m){                 
            return fun(m,n);            
         }          
     };        
 }            
 var b = fun(0).fun(1).fun(2).fun(3);

 先从总体分析一下,就是每次fun函数调用之后都会产生一个新对象,这个对象会将传进来的第一个参数,用闭包的方式保存下来,接下来逐条解释一下这条语句执行时会发生的情况:   

fun(0):   
    fun(0) => n = 0,o = undefined;   
    console.log(o); => o = undefined;打印undefined   
    return { fun: function(m){ return fun(m,n ) } }; 
     => 定义嵌套函数,形成作用域链,
      并由return的对象的属性 fun引用,m = undefined,n =0 ,这条作用域链被保留   
fun(0).fun(1):       
    实际上是调用返回对象的fun方法:       
        function(m)  => m = 1;       
            return fun(m,n) => m=1,n=0;       
     再执行fun函数:       
         fun(n,o) => n=1,o=0;       
         console.log(o) => 打印0       
             return { fun: ...return fun(m,n) } 
                  => m = undefined,n =1; 同上,作用域链被保存 
fun(0).fun(1).fun(2):           
       还是调用了返回对象的fun方法:        
           function(m) => m=2        
               return fun(m,n) m=2,n=1        
                   再调用fun(n,o) => n=2,o=1        
                       然后再打印o就是打印1啦。。。        
最后    fun(0).fun(1).fun(2).fun(3);            
        还是调用了返回对象的fun方法:            
            function(m) => m=3            
                return fun(m,n) m=3,n=2            
                    再调用fun(n,o) => n=3,o=2            
                        然后再打印o就是打印2啦。。。        ...    
                            最后的b是调用了返回对象的fun方法,fun执行的时候又返回了一个对象,
                                所以b是一个对象,里边有一个键为fun值函数的属性,这种属性我们一般叫他方法。

  

其实最后才是我要讲的重点,如果为了炫技或者条件必须得情况下,使用闭包外,闭包能避免就避免,因为只要你使用了闭包就意味着你在内存里划出了一块地方,你的程序可能不一定会用,而电脑在运行时其他的程序却一点不能利用的空间,对程序的性能无疑有影响。

20180917补充

近在整理项目的时候,无意中发现以前技术用with写的语句,为了避免坑,决定研究一下with,其实with是被禁止的,并且是推荐不使用的,所以也没怎么上心。 这次机缘巧合之下,搞了一下with语句,感觉对作用域链的研究更深了。

with是这样的,with(obj) 会将obj对象添加至当前作用域链的最前头,这时候 with(obj){  console.log(key) } , 变量查找时会首先看obj上是否有key,然后才会查找上一级作用域链上的东西。

当然with这一特性,有时候虽然可以很骚,但是是非常不利于后期维护的,所以还是不会用的。

Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post