资讯专栏INFORMATION COLUMN

javascript:闭包的总结

BigNerdCoding / 576人阅读

摘要:当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域,但是闭包情况有所不同。闭包与变量副作用闭包只能取得外层函数中任何变量的最后一个值。可以访问变量,因为这个匿名函数时一个闭包,它能够访问包含作用域中的所有变量。

*前言:这次总结闭包,分别参考了《js高级程序设计》、廖雪峰老师的网站、还有《js忍着秘籍》,好了,废话少说,黑喂狗~~~

---------------------严肃分割线-------------------*

1.js函数中的作用域链

没错,闭包还是要从作用域链说起,要理解闭包必须从函数第一次被调用时发生了什么入手,先看一个例子,代码:

    function compare(value1,value2){
        if(value1 < value2){
            return -1;
        }else if(value1 > value2){
            return 1;
        }else{
            return 0;
        }
    }
    var result = compare(5,10);  //全局作用域中调用

首先定义了一个compare函数,然后又在全局作用域中调用了它。当调用compare()时,会创建一个包含(this,arguments,value1,value2)的活动对象,而全局执行环境的变量对象(this,result,compare)在compare()执行环境的作用域链中处于第二位。在这例子中,当调用compare()函数时,会为函数创建一个执行环境,其作用域链包含两个变量对象:本地活动对象和全局对象,在函数中访问一个变量时,会先从本地作用域中查找,找不到则向上查找外层函数的作用域中是否有,直到全局作用域。当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域,但是闭包情况有所不同。

2.闭包中的作用域链

先看一个例子:

    function createComparisonFunction(propertyName){
        return function(object1,object2){
            var value1 = object1[propertyName];
            var value2 = object2[propertyName];
            
            if(value1 < value2){
                return -1;
            }else if(value1 > value2){
                return 1;
            }else{
                return 0;
            }
        };
    }

    var compareNames  = createComparisonFunction("name");
    var result = compareNames({name:"Jack"},{name:"Rose"});
    compareNames = null;   //销毁匿名函数
    

上面的例子中,在createComparisonFunction函数内部定义了一个匿名函数,它的作用域链包含外部函数createComparisonFunction()的活动对象和全局变量对象,所以匿名函数中可以访问createComparisonFunction()函数中的propertyName。
最重要的是:createComparisonFunction()函数在执行完毕后,它的执行环境作用域链会被销毁,其活动对象仍然会留在内存中,这就是为什么上面的代码中,执行:
var compare = createComparisonFunction("name")之后,createComparisonFunction()函数已经执行完毕,但是仍然可以在下一行代码中执行比较大小的操作。
创建的比较函数被保存在变量compareNames中,设置它等于null,解除该函数引用,垃圾回收。

3.闭包与变量 副作用:

闭包只能取得外层函数中任何变量的最后一个值。看下面例子:

    function createFunctions(){
        var arr = new Array();
        for(var i=0; i<10;i++){
            arr[i] = function(){
                return i;
            };
        }
        return arr;
    }
    
    var result = createFunctions();
    var f1 = result[0];
    var f2 = result[1];
    var f3 = result[2];
    //执行函数
    alert(f1());    //10
    alert(f2());    //10
    alert(f3());    //10

这个例子中似乎每个函数都应该返回自己的索引值,实际上每个匿名函数返回的都是10。因为每个匿名函数中都保存着createFunctions()函数的活动对象,所以它们引用的是同一个变量i,函数createFunctions()返回后,变量i的值都是10,此时每个函数都引用着保存变量i的同一个变量对象,所以每个函数内部i的值都是10.

注意:上述函数的调用过程:执行createFunctions()函数,并且把函数执行结果赋值给变量result,现在result是一个数组,再把result[0]赋值给f1,f1实际上代表的是内部的匿名函数,现在执行这个函数:f1(),就会得到i的值。

改造

可以通过创建另一个匿名函数强制让闭包的行为符合预期,看下面例子:

    function createFunctions(){
        var result = new Array();
        for(var i = 0; i < 10; i++){
            result[i] = (function(num){
                return function(){
                    return num;
                };
            })(i);
        }
        return result;
    }
    
    var final = createFunctions();
    var f1 = final[0];
    alert(f1());   //0

这次没有直接把闭包赋值给数组,而是定义了一个匿名函数,并立即执行该函数的结果赋值给数组。这个匿名函数有一个参数num,也就是最终要返回的值,调用这个匿名函数时,传入了变量i,由于函数参数是按值传递的,所以会把变量i的当前值传递给num,这个匿名函数内部,又创建并返回了一个访问num的闭包,这样,最里面的匿名函数会锁住外层匿名函数传递进来的num值即当前i的值,并且返回num,这样num就会等于此时的i的值并且赋值给数组。

4.闭包就是匿名函数吗?

上述代码中很容易让人误解:闭包就是一个匿名函数,其实不然,看下面例子:

    var outName = "外面的名字";
    var later;
    function outerFunction(){
        var innerName = "里面的名字";
        
        function innerFunction(){
            alert("I can see the "+outName);
            alert("I can see the "+innerName);
        }
        later = innerFunction;
    }
    outerFunction();
    later();

上述代码中,在outerFunction()函数中,将内部函数innerFunction()赋值给全局变量later,执行完倒数第二步:outerFunction();之后,执行later();依然可以可以访问到内部变量innerName。
因为在外部函数outerFunction中声明innerFunction()函数时,不仅声明了函数,还创建了一个闭包,该闭包不仅包含函数声明,还包含了函数声明的那一时刻该作用域中的所有变量。

5.闭包的用处 私有变量

上述例子可以看出,闭包可以用来封装私有变量,就像java中的在对象内部封装一个private私有变量一样。
看下面例子:

    function createCounter(){
        var num = 0;
        this.getNum = function(){
            return num;
        };
        this.num = function(){
            num++;
        };
    }
    
    var counter = new createCounter();
    counter.num()  //调用计数器一次,使num值加1
    //测试代码
    alert(counter.getNum());  //1
    alert(counter.num());    //undefined
    

上面例子中用构造函数模式创建了一个计数器函数,然后对函数进行实例化,在构造函数内部,我们定义了一个变量num,它的可访问性只能在构造器内部,定义了一个getNum()方法,该方法只能对内部变量进行读取,但不能写入。然后又定义了方法num(),通过最后两行测试代码可以看出,可以通过存取方法getNum获取私有变量,但是不能直接访问私有变量。

总结:私有变量的意思是,我们如果想对num进行加减乘除的操作,只能在createCounter内部,外部只能访问内部进行逻辑操作后的值,而不能访问带有对num值进行操作的方法,这样,我们就可以把自己的业务逻辑封装在函数内部的闭包中,只需要暴露出接口让外部获取想要得到的值就可以了,也就是说主动权完全在你定义函数时,外部只能看和获取,而不能进行对变量值的改变的操作。

上述的目的就是创建一个用于访问私有变量的公有方法。看下面代码:

    function Person(name){
        this.getName = function(){
            return name;
        };
        this.setName = function(){
            name = value;
        };
    }
    
    //测试
    var person = new Person("Jack");
    alert(person.getName());     //Jack
    person.setName("Rose");
    alert(person.getName());     //Rose

上面的代码在构造函数内部定义了两个方法:setName和getName,这两个方法都可以在构造函数外部访问和实用,而且都有权访问私有变量name,但在Person构造函数外部,没有任何办法访问name,由于这两个方法是在构造函数内部定义的,所以做为闭包能够通过作用域链访问name。上述在构造函数中定义特权方法有一个缺点,就是必须要使用构造函数模式来达到这个目的,这样针对每个实例都会创建同样一组新方法。

解决办法:静态私有变量
看下面代码:

    (function(){
        var name = "";
        Person = function(value){
            name = value;
        };
        Person.prototype.getName = function(){
            return name;
        };
        Person.prototype.setName = function(value){
            name = value;
        };
    })();
    
    //测试函数
    var person1 = new Person("Jack");
    alert(person1.getName());         //Jack
    person1.setName("Rose");
    alert(person1.getName());         //Rose
    
    var person2 = new Person("Will");
    alert(person1.getName());          //Will
    alert(person2.getName());          //Will
   

上述代码中,Person构造函数与getName()和setName()方法一样,都有权访问私有变量name,name变成了一个静态的、由所有实例共享的属性。在一个实例上调用setName会影响所有的实例。或者新建一个Person实例都会赋予name属性一个新值。

上述两个方法:第二种创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变量。

模仿块级作用域

上面举过例子,js没有块级作用域,可以通过匿名函数立即执行把块级作用域包裹起来,这样就有了块级作用域。看下面代码:

    function outputNumbers(count){
        (function(){
            for(var i = 0; i

上述函数中,在for循环外部插入了一个私有作用域,在匿名函数中定义的任何变量,都会在匿名函数执行结束时被销毁。因此变量i只能在for循环中使用,使用后即被销毁,因此上面的代码执行会这样:
1.执行测试函数后,会弹出5个弹窗,会显示0,1,2,3,4
2.执行完匿名函数后,i即被销毁,所以执行alert(i);会报错。
3.可以访问变量count,因为这个匿名函数时一个闭包,它能够访问包含作用域中的所有变量。

这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数,看下面代码:

    (function(){
        var now = new Date();
        if(now.getMonth() == 0 && now.getDate() == 1){
            alert("元旦快乐");
        }
    })();

上述代码放在全局作用域中,可以用来确定哪一天时1月1日元旦,now是匿名函数中的局部变量,不用在全局作用域中创建它。

先写这么多吧,以后再添加~~~~~

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

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

相关文章

  • 还担心面试官问闭包

    摘要:一言以蔽之,闭包,你就得掌握。当函数记住并访问所在的词法作用域,闭包就产生了。所以闭包才会得以实现。从技术上讲,这就是闭包。执行后,他的内部作用域并不会消失,函数依然保持有作用域的闭包。 网上总结闭包的文章已经烂大街了,不敢说笔者这篇文章多么多么xxx,只是个人理解总结。各位看官瞅瞅就好,大神还希望多多指正。此篇文章总结与《JavaScript忍者秘籍》 《你不知道的JavaScri...

    tinyq 评论0 收藏0
  • JavaScript闭包

    摘要:闭包引起的内存泄漏总结从理论的角度将由于作用域链的特性中所有函数都是闭包但是从应用的角度来说只有当函数以返回值返回或者当函数以参数形式使用或者当函数中自由变量在函数外被引用时才能成为明确意义上的闭包。 文章同步到github js的闭包概念几乎是任何面试官都会问的问题,最近把闭包这块的概念梳理了一下,记录成以下文章。 什么是闭包 我先列出一些官方及经典书籍等书中给出的概念,这些概念虽然...

    HmyBmny 评论0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件开发前端掘金作者原文地址译者插件是为应用添加全局功能的一种强大而且简单的方式。提供了与使用掌控异步前端掘金教你使用在行代码内优雅的实现文件分片断点续传。 Vue.js 插件开发 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins译者:jeneser Vue.js插件是为应用添加全局功能的一种强大而且简单的方式。插....

    izhuhaodev 评论0 收藏0
  • 通过示例学习JavaScript闭包

    摘要:译者按在上一篇博客,我们通过实现一个计数器,了解了如何使用闭包,这篇博客将提供一些代码示例,帮助大家理解闭包。然而,如果通过代码示例去理解闭包,则简单很多。不过,将闭包简单地看做局部变量,理解起来会更加简单。 - 译者按: 在上一篇博客,我们通过实现一个计数器,了解了如何使用闭包(Closure),这篇博客将提供一些代码示例,帮助大家理解闭包。 原文: JavaScript Clos...

    xingpingz 评论0 收藏0
  • Js学习笔记:闭包

    摘要:一前言这个周末,注意力都在学习基础知识上面,刚好看到了闭包这个神圣的东西,所以打算把这两天学到的总结下来,算是巩固自己所学。因此要注意闭包的使用,否则会导致性能问题。五总结闭包的作用能够读取其他函数内部变量。 # 一、前言 这个周末,注意力都在学习基础Js知识上面,刚好看到了闭包这个神圣的东西,所以打算把这两天学到的总结下来,算是巩固自己所学。也可能有些不正确的地方,也请大家看到了,麻...

    Crazy_Coder 评论0 收藏0

发表评论

0条评论

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