浅谈JS中的闭包 

 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域.

变量的作用域

  变量共有两种,一种为全局变量,一种为局部变量.那么全局变量的作用域为: 局部变量只能在定义此变量的函数体内使用,则局部变量的作用域为定义此变量的函数体.而全局变量可以全局使用.局部变量只有在调用函数的时候存在,函数调用完成之后立马就销毁了,而全局变量会一直存在.

  首先我在全局变量中定义一个变量a,var a=0;那我们了解到变量a在内存中的存储是window的指针指向栈中的a,栈中的a的指针再指向堆中的0,如下图所示,所以变量a会一直存在.GC是没有办法回收的.

  

  接下来,我定义一个函数,在函数中定义一个变量b,如下代码所示:

  

function fun(){
    var b=5;
}

  而这时候,变量b在内存中的表现形式如下图所示:

  
  在我们不调用这个函数的时候,并没有初始指针指向b,所有JS的GC会立马把局部变量给清理掉.而在调用函数时,调用函数的指针指向b,b右指向5,所以在调用函数的过程中b是在内存中存在的.而调用函数执行完之后,调用函数指针不再指向b,所以GC有会把变量给清除掉.

  接下来让我们用一个例子来理解一下变量的作用域.

var a =0;
function fun(){
    var b=5;
    console.log(a,b);
}
function fun1(){
    console.log(a,b);
}
fun();
fun1();

  这段代码的输出结果如下图:我们发现在fun1的时候提示我们没有定义b,所以fun1不能访问fun中的局部变量b.

基本的闭包

  在上面例子中,我们可以非常确定的知道fun1并不能访问fun中的局部变量b,但是在我们工作中我们往往需要一个函数访问另外一个函数的局部变量,那这时候我们怎么做呢?创建闭包!创建闭包!创建闭包!重要的事情说三遍.

  闭包的作用:现在我们可以理解为闭包有两个作用:1.读取函数内部的局部变量 2. 让这个变量的值始终保持在内存中.

  听起来有点抽象,我们可以通过一个例子来充分的理解它,代码如下:

function fun() {
    var b = 5;
     function fun1 () {
        alert(b);
    }
    return fun1;
}

var result = fun();
result();

  那我们现在来分析一下这个例子,如下图所示:

  首先我们先声明一个函数fun,然后在函数里面创建一个局部变量b,接着在fun里面定义一个fun1,然后把fun1当做fun函数的返回值返回出去.在函数的外部我们调用fun,把返回的结果赋值给result,那么此时result和fun1是相等的,我调用result就相当于调用了fun1.然后就相当于在fun函数外部,访问了fun函数的内部局部变量.

封闭的作用域

  封闭作用域: 又称值为封闭空间,还有一个昵称叫小闭包,以及匿名函数自调。

  最大目的:全局变量私有化

  作用:

    •   不污染全局空间.
    • 内部所有的临时变量执行完毕都会释放不占内存。
    • 可以保存全局数据
    • 更新复杂变量.

  我们通过实际案例来学习了解这些抽象的概念,代码如下:定义全局变量a,var a =0;这时候我们通过作用域章节会非常清楚的之后变量a会一直存在在内存中,但是如果我们不想让他一直存在,就可以使用闭包实现.如下代码:

  

(function (){
    var a =0;
})();

  这时候,就相当于我们把a放在闭包函数中,a相当于局部变量,从而实现全局变量私有化.这种闭包形式的写法多种,比较常用的有如下几种:

  

   //普通写法
    (function () {

    })();
    //文艺写法
    ;(function () {

    })();
    //其它 二逼写法
    +(function () {

    })();
    -(function () {

    })()

  文艺写法在普通写法的基础上增加了一个; 可以理解为文艺青年怕上句代码没写分号,然后担心编辑器解析的时候当做一句话解析了,所以给前面加个分号.

作用域链条

  在一个作用域中,如果要使用属性的话,那么会做如下两件事儿:

  1、先递归遍历寻找当前作用域中有没有这个属性, 如果有,则用, 如果没有,则会顺着链条继续往上寻找

  2、递归: 多层遍历;直到找到属性为止

  注意点:在作用域链条中,遍历只能从下而上, 不能由上而下

  这个问题很好理解.比如有方法 a中有变量strA,又有方法b,方法b中又有变量strB,如果我们在b使用a,那么系统会在方法b中遍历所有的属性,然后看有没有这个,如果没有这个属性,则顺着作用域链条往上寻找,找方法a中有没有这个属性.就这样依次类推,直到找到属性为止.

  那么在理解了作用域链条以及属性的使用之后,下面有两个建议给大家:

  1、尽量不要使用全局变量

  2、作用域链不要太多

  因为如果使用全局变量,系统沿着作用域链条会找一层一层向上找,直到找到window作用域,而window作用域下的属性非常的多,遍历的时候要执行很多次,所以不建议大家使用全局变量,并且在使用全局变量的时候,一定要注意,它的值什么时候应该改变,什么时候不应该改变,如果这个问题处理不好的话,程序很容混乱,最后你自己都不知道输出的结果时什么东西了,所不建议大家使用太多的全局变量。

  我们来看一个例子,在body里面放三个button标签,然后给它绑定Id btn1 btn2 btn3,接下来要求我们拿到这三个标签.很简单直接通过document.getElementById即可.很快就可以写完,代码如下:

  

var btn1 =  document.getElementById('btn');
var btn2 =  document.getElementById('btn');
var btn3 =  document.getElementById('btn');

  那么我们在理解了作用域链条之后,有没有觉得这个代码可以优化?先在的情况时我每次使用到document的时候都会遍历一遍window,是不是很浪费资源?当然三个button还不明显,如果我有100个标签,是不是我就要遍历100次window?这样很浪费,那我们来想一下,我们的代码可不可以这样改一下:如下所示

  

(function () {
        var a = document;
        var btn1 = d.getElementById('btn');
        var btn2 = d.getElementById('btn');
        var btn3 = d.getElementById('btn');
    })();

  这样修改完之后,我们是不是只需要遍历依次window就可以了?只需要在创建a,给a赋值的时候遍历window,其它情况下只需要遍历闭包里面的属性是不是就可以了?这样效率有没有提高?这也是使用闭包的一个明显的作用,全局变量私有化!!!

闭包经典案例

  其实写到这里,闭包已经基本介绍完了,但是昨天我在睡觉之前,看了一篇微博,博主@Damonare也发了一篇闭包的文章,文章的最后,它放了几道闭包的经典案例,我觉得涵盖面很广几乎涉及到了闭包里面的所有知识,但是他没给解释,那么今天我就分析一下这些案例,帮助一些闭包掌握不好的同学理解理解。

案例1

  

   var name = "The Window";
    var object = {
        name : "My Object",
        getNameFunc : function(){
            return function(){
                return this.name;
            };
        }
    };  
    alert(object.getNameFunc()());

  首先,我们要分析代码,如下图所示:

  

  

  我们来分析一下,输出语句中的objuct.getNameFunc 的值为json中第二对键值对的value,是一个大的方法体function(){

return function(){

return this.name;

};这个没什么疑问.那么我们在继续看objuct.getNameFunc()就是调用第二对键值对的value方法,那么返回值为function(){

return this.name;

};这个方法,也没什么疑问.接下来关键点来了,我们再调用这个返回函数,返回的是this.name,对不对,我们输出的为this.name,那么this.name会是什么呢?我们都知道this所在的函数在哪个对象中, this就指向对象,还可以这样理解,谁调用this所在的那个函数,this就指向谁,那么在这个案例中我们可以这样来分解一下: object.getNameFunc()(),接下来我们来分析一下这句话,是不是可以理解为调用object.getNameFunc()方法,那么谁调用呢?明显是window,那么this就指向window,所以this.name=The Window。

案例2

  我们在案例1的基础上修改一下代码,代码如下:

  

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};
alert(object.getNameFunc()());

  同样,我们也来分析一下代码,如下图所示:

  

  

  上面我们已经分析到了,输出的结果的执行代码为that.name,那么我们看一下that为function(){

var that = this;

return function(){

return that.name;

};的this,那么我们来调用一下这个函数,object.getNameFunc(),这样调没什么问题吧,那么我们就知道了这里的this指向object,那么object是一个json 它的那么为My Object.所以输出的结果为My Object.

案例3

  

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 ,定义一个变量a=fun(0),那么肯定会执行fun中输出第二个参数的代码,而我们并没有传第二个参数,那么输出的结果肯定是undefined.那么这时候

a={

fun:function(m){

return fun(m,n);

}

所以,a.fun(1);就是fun(1,n),由于n是我们定义a的时候传入的参数,所以a.fun(1)===fun(1,0);所以输出0,同理a.fun(2); a.fun(3);输出的结果也都是0.故第一段输出代码输出的结果为undefined 0 0 0。

  接下来我们在看第二句输出代码fun(0).fun(1).fun(2).fun(3);我们先看第一个fun(0),和上面一样输出undefined,那么fun(0).fun(1)也是和上面一样,输出0,不再解释.接下来看fun(0).fun(1).fun(2),因为我们刚才讲了fun(0).fun(1)=== fun(1,0),所以fun(0).fun(1).fun(2)=== fun(1,0).fun(2),就等于fun(2,1),所以输出1,那么fun(2,1).fun(3) 就等于fun(3,2),所以输出结果为2 ,故第二段输出代码输出结果为undefined 0 1 2。

最后我们来看第三段代码,和第二段一样fun(0)输出undefined,fun(0).fun(1)输出0,fun(0).fun(1).fun(2)===fun(2,1) 输出1 fun(0).fun(1).fun(3)===fun(3,1) 输出1 故第三段输出代码输出的结果为undefined 0 1 1

  所有的输出结果如下图所示:

  

总结:

  闭包在js中使用的比较常见,一定要掌握.但是如果最后的案例如果你都可以轻松的说出答案的话,那么恭喜你,闭包你已经理解的比较透彻了.

浅谈JS中的闭包的更多相关文章

  1. 浅谈JS中 var let const 变量声明

    浅谈JS中 var let const 变量声明 用var来声明变量会出现的问题: 1. 允许重复的变量声明:导致数据被覆盖 2. 变量提升:怪异的数据访问.闭包问题 3. 全局变量挂载到全局对象:全 ...

  2. 浅谈JavaScript中的闭包

    浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...

  3. 浅谈JS中的!=、== 、!==、===的用法和区别 JS中Null与Undefined的区别 读取XML文件 获取路径的方式 C#中Cookie,Session,Application的用法与区别? c#反射 抽象工厂

    浅谈JS中的!=.== .!==.===的用法和区别   var num = 1;     var str = '1';     var test = 1;     test == num  //tr ...

  4. 由项目浅谈JS中MVVM模式

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.    背景 最近项目原因使用了durandal.js和knock ...

  5. js架构设计模式——由项目浅谈JS中MVVM模式

    1.    背景 最近项目原因使用了durandal.js和knockout.js,颇有受益.决定写一个比较浅显的总结. 之前一直在用SpringMVC框架写后台,前台是用JSP+JS+标签库,算是很 ...

  6. 浅谈JS中的高级函数

    在JavaScript中,函数的功能十分强大.它们是第一类对象,也可以作为另一个对象的方法,还可以作为参数传入另一个函数,不仅如此,还能被一个函数返回!可以说,在JS中,函数无处不在,无所不能,堪比孙 ...

  7. 浅谈js中的数据类型,使用typeof获取js数据类型

    JS中的数据类型 1):Undefined——值未定义 注:Undefined类型只有一个值,即特色的undefined.在使用var声明变量但未对其加以初始化时,这个变量的值就是undefined ...

  8. 浅谈js中的浅拷贝和深拷贝

    在js中如何把一个对象里的属性和方法复制给另一个对象呢? 下面举一个例子来说明: var person={name:'chen',age:18}; var son={sex:'男'}; functio ...

  9. 浅谈js中null和undefined的区别

    在JS中,null和undefined是经常让人摸不着头脑的东西,尤其是在数据初始化以及处理的过程中,经常稍微不注意,就会让页面在渲染时出现报错,下面来细说下,这两者之间的区别: null 表示一个对 ...

随机推荐

  1. web安全:HTTPS

    E1:搭建zoobar网站开启apache服务和mysql服务service apache2 startservice mysql start 建立数据库和表mysql->create data ...

  2. Android事件监听器Event Listener

    在 Android 中,我们可以通过事件处理使UI与用户互动(UI Events). UI的用户事件处理,即View处理用户的操作,在应用程序中几乎不可避免.View是重要的类,它是与用户互动的前线: ...

  3. Entity Framewor 学习笔记 (include + where)

    如果我们想在子查询做过滤的话应该怎样写呢? IEnumerable<Product> products = db.products.Include(p => p.colors.Whe ...

  4. Altium Designer 导出Gerber文件详细教程

    Altium Designer 导出Gerber文件详细教程   1.用Altium打开需要导出Gerber文件的PCB: 2.点击“File”-“fabricatio Outputs ” “Gerb ...

  5. 手机上使用asmack开发xmpp客户端

    openfire服务端,smack:     下载地址:http://www.igniterealtime.org/downloads/index.jsp     源代码:http://www.ign ...

  6. Hibernate Validation使用示例及讲解

    Hibernate Validation使用示例及讲解 时间 -- :: ITeye-博客 原文 http://wdmcygah.iteye.com/blog/2174680 主题 Java 在项目开 ...

  7. Shell中特殊的变量

    $表示当前的进程,当使用echo $$是会输出当前shell的pid echo $$ 特殊变量列表 变量 含义 $0 当前脚本的文件名 $n 传递给脚本或函数的参数.n 是一个数字,表示第几个参数.例 ...

  8. SQL Server 系统时间

    getdate()函数:取得系统当前的日期和时间.返回值为datetime类型的. 用法:getdate() 例子: select getdate() as dte,dateadd(day,-1,ge ...

  9. iOS socket 实现tcp和服务器长链接的简单使用心得

    首先iOS端用了一个第三方的框架 GCDAsyncSocket 当然这个是CocoaAsyncSocket框架里面的一部分 Github下载地址https://github.com/robbiehan ...

  10. 安装 Android 运行环境

    如果你恰好有一些旧的. 过时的 Android SDK 版本,请务必把所需的包更新至下面提到的版本并安装所有缺少的部分. 安装和配置 SDK 安装最新的 JDK. 使用 brew install an ...