本篇的标题虽然是"jQuery闭包之浅见...",但实际上本篇涉及的多半是javascript"闭包"相关内容,之所以写这个标题,完全是因为自己平常用jQuery写前端习惯了。还有一个原因是:javascript"闭包"很容易造成"内存泄漏", 而jQuery已经自动为我们规避、处理了由"闭包"引发的"内存泄漏"。

javascript的"闭包"是这样定义的:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

定义高度提炼,还是从代码实例来体验"闭包"的来龙去脉吧。本篇主要包括:

从面向对象的角度来理解"闭包"

在面向对象中,我们是这样定义类:

public class SomeClass
{
    private string _someVariable;
 
    public void SomeMethod()
    {
        //TODO:
    }
}

在javascript中,有一种情况是,一个函数中包含了另外一个内部函数:

function OutFunction()
{
    var temp = 0;
    function innerFunction(){
    }
}

我们把OutFunction称为外部函数,把innerFunction称为内部函数。

这里的外部函数OutFunction相当于一个类。
外部函数变量temp相当于类的私有字段。
内部函数innerFunction相当于类的方法。

就像我们需要实例化类来调用类的实例方法一样,在javasript中,当我们在外部函数之外调用内部函数的时候,我们把此时的内部函数叫做"闭包",相当于面向对象的实例方法。

接下来,从"如何调用内部函数"这个角度,来循序渐进地体会何谓"闭包"。

调用内部函数的几种方式

□ 在外部函数之外直接调用内部函数

    <script type="text/javascript">
        function outerFunction() {
            console.log('外部函数执行了');
            function innerFunction() {
                console.log('内部函数执行了');
            }
        }
 
        $(function() {
            innerFunction();
        });
    </script>

结果报错:Uncaught ReferenceError: innerFunction is not defined

以上情况,内部函数的作用域是在外部函数之内,当在外部函数之外调用内部函数的时候,自然就报错。

□ 在外部函数之内调用内部函数:

    <script type="text/javascript">
        function outerFunction() {
            console.log('外部函数执行了');
            function innerFunction() {
                console.log('内部函数执行了');
            }
 
            innerFunction();
        }
 
        $(function() {
            outerFunction();
        });
    </script>

结果:
外部函数执行了
内部函数执行了

以上情况,内部函数的作用域是在外部函数之内,当在外部函数之内调用内部函数,自然不会报错。

□ 在外部函数之外调用内部函数,把内部函数赋值给一个全局变量

    <script type="text/javascript">
        
        //全局变量
        var globalInner;
 
        function outerFunction() {
            console.log('外部函数执行了');
            function innerFunction() {
                console.log('内部函数执行了');
            }
 
            globalInner = innerFunction;
        }
 
        $(function() {
            outerFunction();
            console.log('以下是全局变量调用内部方法:');
            globalInner();
        });
    </script>

结果:
外部函数执行了
以下是全局变量调用内部方法:
内部函数执行了

以上情况,全局变量globalInner相当于面向对象的委托,当把内部函数赋值给全局变量,调用委托方法就会调用内部函数。

□ 在外部函数之外调用内部函数,把内部函数赋值给一个变量:

    <script type="text/javascript">
 
        function outerFunction() {
            console.log('外部函数执行了');
            function innerFunction() {
                console.log('内部函数执行了');
            }
 
            return innerFunction;
        }
 
        $(function() {
            console.log('先把外部函数赋值给变量');
            var temp = outerFunction();
            console.log('再执行外部函数变量');
            temp();
        });
    </script>

结果:
先把外部函数赋值给变量
外部函数执行了
再执行外部函数变量
内部函数执行了

以上情况,我们可以看到,内部函数不仅可以赋值给全局变量,还可以赋值给局部变量。

就像面向对象的方法会用到类的字段,内部函数也会用到变量,接下来体验变量的作用域。

变量的作用域

□ 内部函数变量

    <script type="text/javascript">
 
        function outerFunction() {
            function innerFunction() {
                var temp = 0;
                temp++;
                console.log('内部函数的变量temp的值为:' + temp);
            }
            return innerFunction;
        }
 
        $(function() {
            var out1 = outerFunction();
            out1();
            out1();
 
            var out2 = outerFunction();
            out2();
            out2();
        });
    </script>

结果:
内部函数的变量temp的值为:1
内部函数的变量temp的值为:1
内部函数的变量temp的值为:1
内部函数的变量temp的值为:1

从中我们可以看出内部函数变量temp的生命周期:
→当第一次执行内部函数,JavaScript运行时创建temp变量
→当第二次执行内部函数,JavaScript垃圾回收机制把先前的temp回收,并释放与该temp对应的内存,再创建一个新的内部函数变量temp

.....
所以,每次调用内部函数,内部函数的变量是全新的。也就是说,内部函数的变量与内部函数同生共灭。

□ 全局变量

   <script type="text/javascript">
        //全局变量
        var temp = 0;
        function outerFunction() {
            function innerFunction() {
                temp++;
                console.log('全局变量temp的值为:' + temp);
            }
            return innerFunction;
        }
 
        $(function() {
            var out1 = outerFunction();
            out1();
            out1();
 
            var out2 = outerFunction();
            out2();
            out2();
        });
    </script>

结果:
全局变量temp的值为:1
全局变量temp的值为:2
全局变量temp的值为:3
全局变量temp的值为:4

可见,全局变量供外部函数和内部函数共享。

□ 外部函数变量

    <script type="text/javascript">
        function outerFunction() {
            var temp = 0;
            function innerFunction() {
                temp++;
                console.log('外部函数变量temp的值为:' + temp);
            }
            return innerFunction;
        }
 
        $(function() {
            var out1 = outerFunction();
            out1();
            out1();
 
            var out2 = outerFunction();
            out2();
            out2();
        });
    </script>

结果:
外部函数变量temp的值为:1
外部函数变量temp的值为:2
外部函数变量temp的值为:1
外部函数变量temp的值为:2

可见,外部函数的变量与外部函数同生共灭。

以上情况,更接近与"闭包"的原型。有如下几个要素:
1、外部函数
2、外部函数变量
3、内部函数

当我们在外部函数之外调用内部函数的时候,这时的内部函数就叫做"闭包",可以理解为面向对象的实例方法。"闭包"与外部函数变量的"外部环境"是外部函数,他俩与外部函数同生共灭。

一个外部函数中可以有多个内部函数,当调用"闭包"的时候,多个"闭包"共享外部函数变量:

   <script type="text/javascript">
        function outerFunction() {
            var temp = 0;
            function innerFunction1() {
                temp++;
                console.log('经innerFunction1,外部函数变量temp的值为:' + temp);
            }
            
            function innerFunction2() {
                temp += 2;
                console.log('经innerFunction2,外部函数变量temp的值为:' + temp);
            }
 
            return {'fn1' : innerFunction1, 'fn2' : innerFunction2};
        }
 
        $(function() {
            var out1 = outerFunction();
            out1.fn1();
            out1.fn2();
            out1.fn1();
 
            var out2 = outerFunction();
            out2.fn1();
            out2.fn2();
            out2.fn1();
        });
    </script>
 

结果:
经innerFunction1,外部函数变量temp的值为:1
经innerFunction2,外部函数变量temp的值为:3
经innerFunction1,外部函数变量temp的值为:4
经innerFunction1,外部函数变量temp的值为:1
经innerFunction2,外部函数变量temp的值为:3
经innerFunction1,外部函数变量temp的值为:4

jQuery中的"闭包"

"闭包"在jQuery中最常见的应用是,当Document加载完毕再执行jQuery部分:

    <script type="text/javascript">
        $(document).ready(function() {
            var temp = 0;
 
            function innerFunction() {
                temp++;
                console.log(temp);
            }
 
            innerFunction();
            innerFunction();
        });
    </script>

结果:
1
2

可见,$(document).ready()的参数就是一个匿名外部函数,匿名函数内的函数是内部函数。

把元素的事件也可看作是内部函数:

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
    <script src="../Scripts/jquery-2.1.1.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            var counter = 0;
 
            $('#btn1').click(function(event) {
                counter++;
                console.log(counter);
            });
 
            $('#btn2').click(function(event) {
                counter--;
                console.log(counter);
            });
        });
    </script>
</head>
<body>
    <input id="btn1" type="button" value="递增"/>
    <input id="btn2" type="button" value="递减"/>
</body>
 

可见,2个input元素的click事件看作是内部函数,共享同一个外部函数变量counter。

在循环体遍历中,把每次遍历的元素事件也可看作是内部函数:

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
    <script src="../Scripts/jquery-2.1.1.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            for (var i = 0; i < 3; i++) {
                $('<div>Print ' + i + '</div>').click(function() {
                    console.log(i);
                }).insertBefore('#results');
            }
        });
    </script>
</head>
<body>
    <div id="results"></div>
</body>

页面呈现的结果如预期:
Print 0
Print 1
Print 2

可当点击每个div的时候,原本以为控制器台应该显示:0, 1, 2,但实际显示的始终是3,为什么?
--i看作是匿名外部函数的"自由变量",当页面加载完毕后,i就变成了3。div的每次点击看作是内部函数的闭环,而所有的闭环都共享了值为3的这个变量。

我们可以使用jQuery的each()方法来解决以上问题,遍历一个数组,每一次遍历元素值都不同:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
    <script src="../Scripts/jquery-2.1.1.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            $.each([0, 1, 2], function(index, value) {
                $('<div>Print ' + value + '</div>').click(function() {
                    console.log(value);
                }).insertBefore('#results');
            });
        });
    </script>
</head>
<body>
    <div id="results"></div>
</body>

内存泄漏

内存泄漏可以狭隘地理解为:应用程序中变量对应一块内存,由于某些原因(比如代码上的漏洞),始终没有对变量及时实施手动或自动垃圾回收,内存没有得到及时释放,造成内存泄漏。

在JavaScript中,如果对象间的关系比较简单:

以上,A有一个属性指向B,B有一个属性指向C,当A被回收的时候,没有依赖B的对象,B随之被自动回收,C也被最终回收。

当对象间的关系比较复杂,比如存在循环引用的时候,如下:

以上,A有一个属性指向B, B有一个属性指向C,而C又有一个属性指向B,B和C之间存在循环引用。当A被回收之后,B和C是无法自动被回收的,在JavaScript中应该手动处理回收。

JavaScript闭包有可能会造成内存泄漏:

→内部函数闭包引发的内存泄漏

        $(function() {
            var outerVal = {};
 
            function innerFunction() {
                console.log(outerVal);
            }
 
            outerVal.fn = innerFunction;
            return innerFunction;
        });

以上,outVal是在内存中的一个对象,而内部函数innerFunction同样是内存中的一个对象。对象outVal的属性fn指向内部函数,而内部函数通过console.log(outerVal)引用outVal对象,这样outVal和内部函数存在循环引用的情况,如果不手动处理,就会发生"内存泄漏"。

如果,我们在内部函数中不显式引用outerVal对象变量,会造成"内存泄漏"吗?

        $(function() {
            var outerVal = {};
 
            function innerFunction() {
                console.log('hello');
            }
 
            outerVal.fn = innerFunction;
            return innerFunction;
        });

答案是:会的。因为,当内部函数被引用、调用的时候,即使内部函数没有显式引用外部函数的变量,也会隐式引用外部函数变量。

→元素事件引发的内存泄漏

在IE中,如下写法会造成内存泄漏:

$(document).ready(function(){
    var button = document.getElementById("btn");
    button.onclick = function(){
        console.log('hello');
        return false;
    }
});

而如下JavaScript写法不会造成内存泄漏:

function hello(){
    console.log('hello');
    return false;
}
 
$(document).ready(function(){
    var button = docuemtn.getElementById('btn');
    button.onclick = hello;
});

而在jQuery中,类似的写法就不用担心内存泄漏了,因为jQuery为我们做了自动处理来规避内存泄漏。

$(document).ready(function(){
    var $button = $('#btn');
    $button.click(function(event){
        event.preventDefault();
        console.log('hello');
    });
});

总结

与"闭包"相关的包括:变量的作用域、javascript垃圾回收、内存泄漏,需在实践多体会。

jQuery闭包之浅见,从面向对象角度来理解的更多相关文章

  1. jquery 闭包

    jQuery 闭包结构 1 2 3 4 5 6 7 // 用一个函数域包起来,就是所谓的沙箱 // 在这里边 var 定义的变量,属于这个函数域内的局部变量,避免污染全局 // 把当前沙箱需要的外部变 ...

  2. JavaScript函数、闭包、原型、面向对象

    JavaScript函数.闭包.原型.面向对象 断言 单元测试框架的核心是断言方法,通常叫assert(). 该方法通常接收一个值--需要断言的值,以及一个表示该断言目的的描述. 如果该值执行的结果为 ...

  3. JQuery插件让图片旋转任意角度且代码极其简单 - 摘自网友

    JQuery插件让图片旋转任意角度且代码极其简单 2012-04-01 09:57:03     我来说两句      收藏    我要投稿 引入下方的jquery.rotate.js文件,然后通过$ ...

  4. 从执行上下文(ES3,ES5)的角度来理解"闭包"

    目录 介绍执行上下文和执行上下文栈概念 执行上下文 执行上下文栈 伪代码模拟分析以下代码中执行上下文栈的行为 代码模拟实现栈的执行过程 通过ES3提出的老概念-理解执行上下文 1.变量对象和活动对象 ...

  5. 从需求的角度去理解Linux系列:总线、设备和驱动

    笔者成为博客专家后整理以前原创的嵌入式Linux系列博文,现推出以让更多的读者受益. <从需求的角度去理解linux系列:总线.设备和驱动>是一篇有关如何学习嵌入式Linux系统的方法论文 ...

  6. 03.JavaScript 面向对象精要--理解对象

    JavaScript 面向对象精要--理解对象 尽管JavaScript里有大量内建引用类型,很可能你还是会频繁的创建自己的对象.JavaScript中的对象是动态的. 一.定义属性 当一个属性第1次 ...

  7. Android AsyncTask完全解析,带你从源码的角度彻底理解

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进 ...

  8. 从逆向的角度去理解C++虚函数表

    很久没有写过文章了,自己一直是做C/C++开发的,我一直认为,作为一个C/C++程序员,如果能够好好学一下汇编和逆向分析,那么对于我们去理解C/C++将会有很大的帮助,因为程序中所有的奥秘都藏在汇编中 ...

  9. [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...

随机推荐

  1. Linux学习笔记:touch新建文件、修改访问、改动时间

    touch用于创建新的空文件或者修改已有文件的时间戳. 语法:touch file.txt 如果file存在,使用touch指令可更改这个文件或目录的日期时间,包括存取时间和更改时间. 如果file不 ...

  2. Delphi与Socket

    一.Delphi与Socket计算机网络是由一系列网络通信协议组成的,其中的核心协议是传输层的TCPIP和UDP协议.TCP是面向连接的,通信双方保持一条通路,好比目前的电话线,使用telnet登陆B ...

  3. 浅谈C#中的值类型和引用类型

    在C#中,值类型和引用类型是相当重要的两个概念,必须在设计类型的时候就决定类型实例的行为.如果在编写代码时不能理解引用类型和值类型的区别,那么将会给代码带来不必要的异常.很多人就是因为没有弄清楚这两个 ...

  4. sql server2012 企业版 百度云下载

    链接: https://pan.baidu.com/s/1j7a6RWwpvSzG-sF7Dnexfw 提取码: 关注公众号[GitHubCN]回复获取  

  5. CCF CSP 201509-3 模板生成系统

    CCF计算机职业资格认证考试题解系列文章为meelo原创,请务必以链接形式注明本文地址 CCF CSP 201509-3 模板生成系统 问题描述 成成最近在搭建一个网站,其中一些页面的部分内容来自数据 ...

  6. 【POJ】2449.Remmarguts' Date(K短路 n log n + k log k + m算法,非A*,论文算法)

    题解 (搬运一个原来博客的论文题) 抱着板题的心情去,结果有大坑 就是S == T的时候也一定要走,++K 我发现按照论文写得\(O(n \log n + m + k \ log k)\)算法没有玄学 ...

  7. Ionic实战六:日期选择控件

    onic日期选择控件,用于ionic项目开发中的日期选择以及日期插件   

  8. 基于 Laravel 开发博客应用系列 —— 项目必备软件安装

    1.概述 通过本项目我们将会构建一个简单.清爽.优雅的博客系统,以及维护管理该博客的后台. 本项目源码公开在GitHub上:https://github.com/ChuckHeintzelman/l5 ...

  9. 关于谷歌浏览器62版本之后引用video.js不能自动播放的问题(Cross-origin plugin content from http://vjs.zencdn.net/swf/5.0.0-rc1/video-js.swf must have a visible size larger than 400 x 300 pixels, or it will be blocked.)

    Cross-origin plugin content from http://vjs.zencdn.net/swf/5.0.0-rc1/video-js.swf must have a visibl ...

  10. gp数据库运维

    最近需要将一份db2导出的历史数据入库gp集群,然后把每天的增量数据导出成txt文件和对应的log日志,再ftp传输给另外一台机器.其中陆续碰到一些坑,在此记录 历史文件数据清洗 列分隔符的选择 碰到 ...