JavaScript 闭包详解

JavaScript 闭包详解JavaScript闭包文章目录JavaScript闭包一、为什么要闭包二、外部得以访问函数内变量三、某些变量得以常驻内存1.垃圾回收机制对闭包的处理2.结合立即执行函数来保存某些变量总结#前言##1.什么是闭包函数闭包函数是声明在另一个函数内的函数,是被嵌套在父函数内部的子函数,在《JS高级程序设计-第3版》中对闭包解释是:”闭包是指有权访问另外一个函数作用域中的变量的函数.”闭包函数可以访问[包裹其的函数]内的各种参数和变量,即便外部函数已经执行完毕.(至于为什么请看下文).一、为什么

大家好,又见面了,我是你们的朋友全栈君。

JavaScript闭包


# 前言-什么是闭包函数 闭包函数是声明在另一个函数内的函数,是被嵌套在父函数内部的子函数,在《JS高级程序设计-第3版》中对闭包解释是:”闭包是指有权访问另外一个函数作用域中的变量的函数.” 闭包函数可以访问[包裹其的函数]内的各种参数和变量,即便外部函数已经执行完毕.(至于为什么请看下文).

一、为什么要闭包

  1. 使外部得以访问函数内部的变量;
  2. 避免全局变量的使用,防止全局变量污染(匿名函数);
  3. 让某些关键变量得以常驻内存,免于被回收销毁(闭包函数);

二、让某些变量得以常驻内存

我们需要将立即执行函数与闭包结合;
我们都知道JavaScript是自带垃圾回收机制的,对于函数来说,在其执行完毕后会被垃圾回收机制回收来进行内存释放,函数内部的局部对象(各种变量之类)也会被连同销毁使内存中仅仅保存全局作用域.

1.原理

前言说到闭包函数就是一个嵌套在父函数里面并且有使用父函数变量的子函数, 闭包函数的执行必定依赖于父函数提供的数据,但要是调用闭包函数时父函数已经被销毁,闭包函数怎么执行呢?
没法执行,因为闭包函数所依赖的变量也都被销毁,总不能因为要执行闭包函数再把父函数提出来,不太合理;

所以不能就这么回收掉,但是保存整个父函数又有点离谱,所以JavaScript垃圾回收机制只会保存闭包函数在父函数中所依赖到的变量这些被保存起来的变量不会被内存回收器回收,直到确认子函数不会再被调用(子函数被删除或者失去指针)为止;

类比一下,如果你学过webpack的话,应该会知道webpack在打包一个文件的时候会把这个文件依赖的所有文件一起打包,就是为了防止使用的时候出问题,垃圾回收机制是在删减的时候留下需要的,weboack是在打包的时候加上需要的.

2.Why 立即执行函数?

我想探讨一下为什么推荐用立即执行函数来配合闭包进行变量保存…
一开始我猜为了在闭包函数保存完需要的变量后父函数能被及时回收释放内存,才采用了匿名立即执行函数来作为闭包函数的父函数.因为立即执行函数自我回调执行完成后会被立即销毁回收,用一次就释放,节约内存(但因为销毁快,外界无法引用其内部的变量)
后来看到了一个例子,作者将使用了立即执行函数的闭包和没有使用立即执行函数的闭包进行了比较,让我改变了想法:

//例1,这个例子中没有使用立即执行函数;
function createFunction() { 
   
    var Array = [];
    for( var i = 0; i<10; i++) { 
   
    //将函数赋值给每个数组元素;
        Array[i] = function() { 
   
            return i;
        };
    }
    return Array;
}

var aa = createFunction();
console.log(aa[0]()); //10
console.log(aa[1]()); //10

由于作用域链的配置机制,因为每个函数的作用域链中保存的都是createFunctions()的活动对象,所以每个函数引用的都是活动对象中的同一个变量 i。
(活动对象: 在JavaScript中,当一个函数被创建时最后一步便是活动对象推入作用域链,函数中访问一个变量时会从作用域链中搜索具有相应名字的变量,函数执行完后局部活动对象会被销毁,活动对象中包含了参数列表和arguments对象等属性. 活动对象包含变量对象所有的属性)
当createFunctions() 函数执行结束返回后,变量 i 的值就已经固定为10,而每个函数保存的变量对象里的 i 都出自createFunctions()的活动对象,每个函数拿到的 i 都是出自同一个活动对象的,都一样,所以最后不论输出哪个数组元素得到的都是10.
即说明了闭包中所保存的是整个活动对象,而不是某个具体的变量,这种机制并不是我们想要的,我们希望它能把每个变量单独保存下来,所以就有了能解决这个问题的,使用了立即执行函数的例子,即例2:

function createFunction() { 
   
    var result = new Array();
    for( var i = 0; i<10; i++) { 
   
    
        result[i] = function(num) { 
   
        //每接收一个num就会创建新的一个函数作用域;
            return function() { 
   
            //在每个作用域的内部创建并返回一个返回num的闭包函数
                return num;
            };
        }(i);
     //变量i的当前值会作为实参赋值给上面的形参num;
     
    }
    return result;
}
//在外部使用函数内变量;
var bb = createFunction();
alert(bb[0]()); //0
alert(bb[1]()); //1

闭包函数依赖到了外部立即执行函数的num,所以num会连同闭包函数被保存下来免于销毁,这样result[ ]中被赋值进去的每个函数都能返回一个自己的num,我们的目的就能达到了,完成这一目标的关键就是使用了立即执行函数.

这个闭包函数的父函数函数每接收一个num就会创建新的一个函数作用域(见例3),作用域中传入i后,变量i的当前值会作为实参赋值给上面的形参num,而在当前每个作用域的内部,又创建并返回了一个返回num的闭包函数。这样一来传入每个函数作用域中闭包函数的num就是不同的了.如此类推,被赋值进入result数组中的每个函数作用域都有一个自己num(其实是时num副本),可以返回各自不同的数值了.

for(var i = 0; i < 5; i++) { 
   
  abc(i);
};

function abc(i) { 
   
  setTimeout(function() { 
   
     console.log(i);			// 0 1 2 3 4 
  }, 200); 
}
//这里就相当于创建了5个函数作用域;

可见立即执行函数在保存变量时泛用性比普通函数强;

三、让外部得以访问函数内变量

外部访问函数内变量跟立即执行函数没什么必然关系,不使用立即执行函数也可以进行保存,上面说到的结合立即执行函数的写法只是针对某些特殊情况下无法依据需求保存变量的问题,我们不得不承认立即执行函数泛用性好一些.

在外部调用父函数即可拿到闭包函数内的变量;

四、立即执行函数

刚学到的,单独开一篇感觉也没必要,正好这里用到了就写下来吧…

//这两种写法是会报错的;
(function() { 
   
//函数体;
 })(); 

function() { 
   
//函数体;
 }();

JavaScript引擎先看到了你的”function”关键字,然后就开始以函数声明标准规范你后续的代码,最终JavaScript引擎发现你用一个小括号结束了你的函数,它觉得这是错的.
我们不能否定它判定的规则,人家认为写了function就是要声明函数,那我们就不要上来直接写function了:

var myFunction = function () { 
   
                   /* 函数体 */ 
                 }();
var myObj = { 
   
    myFunction: function () { 
   
                  /* 函数体 */ }
                }();

让JavaScript引擎先看到小括号而不是function关键字,它就会觉得你在写函数表达式,也就判定为合理了;


总结

比较重要的是闭包会造成内存泄漏.闭包会把一些东西永驻保存下来,而且前面提到的它所依赖的东西都不会被销毁,自己的局部活动对象和依赖到的活动对象都会被包含到它自己的作用域链里,所以它的体量往往是比普通函数大上老些;

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/148667.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • vscode设置终端_vscode 关联PDF

    vscode设置终端_vscode 关联PDFhttps://blog.csdn.net/qq_36743482/article/details/103487025

  • 怎么把sql删干净_sql的导入和导出的好处

    怎么把sql删干净_sql的导入和导出的好处程序数据库(PDB)文件保存着调试和项目状态信息,使用这些信息可以对程序的调试配置进行增量链接。在使用/debug生成时,会创建一个PDB文件。可以使用/debug:full或/debug:pdbonly生成应用程序。使用/debug:full生成将产生可调试的代码。使用/debug:pdbonly生成将产生PDB,但是不会产生通知JIT编译器调试信息可用…

  • android在eclipse环境下开发需要什么支持_eclipse环境配置教程

    android在eclipse环境下开发需要什么支持_eclipse环境配置教程eclipse中android环境配置java环境配置java下载去Oracle官网下载自己需要的java版本我这里选择的是windows的jdk8ps:下载需要登录自己Oracle账号,注册登录一下就行下载之后的exe文件双击开,安装到你需要安装的位置即可,我这里安装位置是D:\ProgramFiles\Java\jdk1.8.0_271环境配置在系统变量里面加入了变量JAVA_HOME,值为安装的位置然后在Path里面加入了%JAVA_HOME%\bin和%JAVA_HOME

  • nodejs开发http接口

    nodejs开发http接口目录nodejs的启动方式安装依赖生成package.json新建app.js启动服务调用接口nodejs的启动方式使用nodenodeapp.js使用nodemonnodemonapp.js可以将其配置到package.json的script:start中,然后调用npmstart安装依赖express是一个web应用开发框架nodemon可以用来启…

  • qtcpsocket编程_qtcpsocket判断连接状态

    qtcpsocket编程_qtcpsocket判断连接状态QTcpSocket和QTcpServer类实现了Qt的Tcp客户端和服务器。 tcp是一个流式协议。对于应用程序来说,数据是一个很长的流,有点像一个巨大的文件。 搞成此的协议建立在面向块的tcp协议(Block-oriented)或面向行(Line-oriented)的tcp协议上。 面向块的tcp协议,数据被当作一个2进制的块来传输。没每一个块被当作一个定义了大小的,后面

  • Activiti教程(六)activiti的流程设计_未完待续

    Activiti教程(六)activiti的流程设计_未完待续一.idea配置activiti插件二.流程设计使用idea设计各种流程图并讲解流程空间的各自属性 

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号