当前位置: 首页 > news >正文

用垃圾回收机制解释JavaScript中的闭包

说起javascript中的闭包,首先要知道为什么会存在闭包,其作用又是什么。且为什么闭包中就能让外层函数的变量始终保存呢?下面我们将从这两个角度去剖析它。当然,大神绕道,谢谢哈。

开门见山,直接总结闭包的两大核心作用:

  1. 读取函数内部的变量;
  2. 让变量始终保存在内存中。

一、读取函数内部的变量

众所周知,变量在javascript中有全局变量和局部变量,函数内部可以访问外部的全局变量,而外部无法访问函数内部的局部变量,这是JS的一个特点:

var n = 999;
function f1() {
    console.log(n) 
}
f1(); // 999 
复制代码

那么摆在我们面前的一个问题就是:如何在函数外部访问到函数内部的变量?有一种办法就是在函数内部再定义一个函数,通过这个内层函数去访问外层函数的变量,因为内层函数同属外层函数的作用域中(符合链式作用域结构规则,子对象可以一级一级地向上寻找所有父对象的变量):

funtion f1() {
    var n = 999;
    function f2() {
        console.log(n);
    }
    return f2;
}

var result = f1();
result(); // 999
复制代码

对此:阮一峰老师给出了一个通俗的关于闭包的解释: 闭包就是能够读取其他函数内部变量的函数(关于到底是内层函数是闭包还是外层函数是闭包的解释各方不一致,但这不是重点)。

由于在JS中只有函数内部的子函数才能读取局部变量,因此也可以理解为定义在一个函数内部的函数。

二、让变量始终保存在内存中

暂且我们说闭包是指有权访问另一个函数作用域中的变量的函数吧,函数内部的函数使用到外层函数的变量,使得外层函数的变量的生存时间延长,造成常驻内存。

function foo(){
    var a = 2;
    return function(){
      a += 1;
      console.log(a);
   }
}

var baz = foo();

baz(); // 3
baz(); // 4
baz(); // 5
baz(); // 6
复制代码

上面的例子为什么每次调用baz时,a都没有被初始化赋值呢? 接下来就要从JS的垃圾回收机制去考虑了。

1. 垃圾回收机制

通常我们可以使用引用计数法判断代码中的变量是否被释放,即语言引擎中有一张“引用表”,保存了内存里面所有的资源(通常是各种值)的引用次数,如果一个值的引用次数为0,则表示该值不再用到了,因此改值也被内存释放了。比如:

cont arr = [1,2,3];
// 数组[1,2,3]是一个值,会占用内存变量arr是仅有的对这个数组的引用,因此引用次数为1。尽管只赋值一次,后面代码中再没有调用,但却依然占据内存.
复制代码

譬如JS这样的高级程序语言中都嵌入了一种称为垃圾回收器的机制,其工作是跟踪内存的分配和使用,以便发现任何时候不再需要的内存并对其进行释放。举例如下:

var o1 = {
    o2: {
        x: 1
    }
};
//创建2个对象o2和o1,其中o2被o1对象引用作为其属性,此时没有垃圾可收集

var o3 = o1; //创建变量o3,引用由o1指向的对象的变量
o1 = 1 ; //现在将o1重新赋值为1,最初的o1中的对象由o3变量表示
var o4 = o3.o2; //创建变量o4,引用对象o2,此时o2被两个地方引用:一个是作为o3变量的属性,一个是作为o4变量
o3 = '666'; // 此时最初o1对象应没有再被引用了,可以被垃圾收集了,但是最初的o2还在被o4引用,因此还不能被垃圾收集

o4 = 16; //此时 o2也可以说再见了...
复制代码

2. 解释内存保留

好了,现在再回过头来看看上面那个闭包题,用垃圾回收的思想来作出解答:

function foo(){
    var a = 2;
    function outer() {
        a += 1;
	console.log(a);
    }
    return outer
}

var baz = foo(); //在全局作用域下创建变量baz

复制代码

此时,baz指向的就是 outer,所以outer始终都没有被销毁,而根据垃圾回收机制,由于在outer中有引用外层函数的变量a,因此a也一直没有被销毁,所以就出现这种现象:

baz(); //3
baz(); //4
baz(); //5
复制代码

是不是以下就明白了,接下来看一个简单的经典例题

三、经典例题

看一下经典的面试题吧,用垃圾回收的思想来思想一下结果,就会很顺利:

var callbacks;
for(var i = 0 ; i <= 5 ;i ++) {
   callbacks = function() {
      console.log(i);
   }
}
callbacks(); //6
callbacks(); //6
callbacks(); //6
复制代码

参考文献

  1. 阮一峰老师 《学习Javascript闭包》 www.ruanyifeng.com/blog/2009/0…
  2. 《重读JavaScript高级程序设计》 reng99.cc/2018/03/01/…

相关文章:

  • 阿里云Kubernetes容器服务上体验Knative
  • 用编码器-解码器-重构器框架实现英语-日语的神经机器翻译
  • netty-客户端.channel()方法 源码分析.md
  • Java 多线程编程之:notify 和 wait 用法
  • django之配置静态文件
  • 区块链多币种测试网络钱包(开源)
  • 滴滴出行基于RocketMQ构建企业级消息队列服务的实践
  • TypeScript实现数据结构(一)栈,队列,链表
  • 阿里云移动端播放器高级功能介绍
  • CentOS 7 防火墙操作
  • React-flux杂记
  • Computed property XXX was assigned to but it has no setter
  • 阿里云服务器如何修改远程端口?
  • go的基本知识
  • extract-text-webpack-plugin用法
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • Java方法详解
  • Mac转Windows的拯救指南
  • Sequelize 中文文档 v4 - Getting started - 入门
  • Spark RDD学习: aggregate函数
  • SpriteKit 技巧之添加背景图片
  • Vue 动态创建 component
  • vue-loader 源码解析系列之 selector
  • 从伪并行的 Python 多线程说起
  • 第十八天-企业应用架构模式-基本模式
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 机器学习学习笔记一
  • 基于 Babel 的 npm 包最小化设置
  • 跨域
  • 理解在java “”i=i++;”所发生的事情
  • 利用DataURL技术在网页上显示图片
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • ​ssh-keyscan命令--Linux命令应用大词典729个命令解读
  • #微信小程序(布局、渲染层基础知识)
  • (06)金属布线——为半导体注入生命的连接
  • (1)bark-ml
  • (arch)linux 转换文件编码格式
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (补)B+树一些思想
  • (动态规划)5. 最长回文子串 java解决
  • (三)Hyperledger Fabric 1.1安装部署-chaincode测试
  • (十三)Flask之特殊装饰器详解
  • (四)c52学习之旅-流水LED灯
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • (已解决)什么是vue导航守卫
  • ***利用Ms05002溢出找“肉鸡
  • *上位机的定义
  • .NET 使用 JustAssembly 比较两个不同版本程序集的 API 变化
  • /etc/motd and /etc/issue
  • /proc/interrupts 和 /proc/stat 查看中断的情况
  • [.NET 即时通信SignalR] 认识SignalR (一)
  • [20171102]视图v$session中process字段含义
  • [2019.3.5]BZOJ1934 [Shoi2007]Vote 善意的投票