js闭包和垃圾回收机制示例详解


Posted in Javascript onMarch 01, 2021

前言

闭包和垃圾回收机制常常作为前端学习开发中的难点,也经常在面试中遇到这样的问题,本文记录一下在学习工作中关于这方面的笔记。

正文

 1.闭包

闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。作为一个JavaScript开发者,理解闭包十分重要。

1.1闭包是什么?

闭包就是一个函数引用另一个函数的变量,内部函数被返回到外部并保存时产生,(内部函数的作用域链AO使用了外层函数的AO)

       因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量,但是不必要的闭包只会增加内存消耗。

闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。或者说闭包就是子函数可以使用父函数的局部变量,还有父函数的参数。

1.2闭包的特性

①函数嵌套函数

②函数内部可以引用函数外部的参数和变量

③参数和变量不会被垃圾回收机制回收

1.3理解闭包

基于我们所熟悉的作用域链相关知识,我们来看下关于计数器的问题,如何实现一个函数,每次调用该函数时候计数器加一。

var counter=0;
 function demo3(){
 console.log(counter+=1); 
 }
 demo3();//1
 demo3();//2
 var counter=5;
 demo3(); //6

上面的方法,如果在任何一个地方改变counter的值 计数器都会失效,javascript解决这种问题用到闭包,就是函数内部内嵌函数,再来看下利用闭包如何实现。

function add() {
 var counter = 0;
 return function plus() {
 counter += 1;
 return counter
 } 
 }
 var count=add()
 console.log(count())//1
 var counter=100
 console.log(count())//2

上面就是一个闭包使用的实例 ,函数add内部内嵌一个plus函数,count变量引用该返回的函数,每次外部函数add执行的时候都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址,把plus函数嵌套在add函数内部,这样就产生了counter这个局部变量,内次调用count函数,该局部变量值加一,从而实现了真正的计数器问题。

1.4闭包的主要实现形式

这里主要通过两种形式来学习闭包:

①函数作为返回值,也就是上面的例子中用到的。

function showName(){
 var name="xiaoming"
 return function(){
  return name
 }
 }
 var name1=showName()
 console.log(name1())

闭包就是能够读取其他函数内部变量的函数。闭包就是将函数内部和函数外部连接起来的一座桥梁。

②闭包作为参数传递

var num = 15
            var foo = function(x){
                if(x>num){
                    console.log(x)
                }  
            }
            function foo2(fnc){
                var num=30
                fnc(25)
            }
            foo2(foo)//25

上面这段代码中,函数foo作为参数传入到函数foo2中,在执行foo2的时候,25作为参数传入foo中,这时判断的x>num的num取值是创建函数的作用域中的num,即全局的num,而不是foo2内部的num,因此打印出了25。

1.5闭包的优缺点

优点:

①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突

②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)

③匿名自执行函数可以减少内存消耗

缺点:

①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;

②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。

1.6闭包的使用

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
 
 console.log( i);
 
}, 1000);

}


console.log(i);

我们来看上面的问题,这是一道很常见的题,可这道题会输出什么,一般人都知道输出结果是 5,5,5,5,5,5,你仔细观察可能会发现这道题还有很多巧妙之处,这6个5的输出顺序具体是怎样的?5 -> 5,5,5,5,5 ,了解同步异步的人也不难理解这种情况,基于上面的问题,接下来思考如何实现5 -> 0,1,2,3,4这样的顺序输出呢?

for (var i = 0; i < 5; i++) {
 (function(j) { // j = i
  setTimeout(function() {
  console.log( j);
  }, 1000);
 })(i);
 }
 console.log( i);
//5 -> 0,1,2,3,4

这样在for循环种加入匿名函数,匿名函数入参是每次的i的值,在同步函数输出5的一秒之后,继续输出01234。

for (var i = 0; i < 5; i++) {
 setTimeout(function(j) {
  console.log(j);
 }, 1000, i);
 }
 console.log( i);
 //5 -> 0,1,2,3,4

仔细查看setTimeout的api你会发现它还有第三个参数,这样就省去了通过匿名函数传入i的问题。

var output = function (i) {
  setTimeout(function() {
 
 console.log(i);
 
}, 1000);

};


for (var i = 0; i < 5; i++) {
 
 output(i); // 这里传过去的 i 值被复制了

}


console.log(i);

//5 -> 0,1,2,3,4

这里就是利用闭包将函数表达式作为参数传递到for循环中,同样实现了上述效果。

for (let i = 0; i < 5; i++) {
    setTimeout(function() {
      
  console.log(new Date, i);
    
}, 1000);

}

console.log(new Date, i);

//5 -> 0,1,2,3,4

知道let块级作用域的人会想到上面的方法。但是如果要实现0 -> 1 -> 2 -> 3 -> 4 -> 5这样的效果呢。

for (var i = 0; i < 5; i++) {
  (function(j) {
 
 setTimeout(function() {
  
 console.log(new Date, j);
 
}, 1000 * j); // 这里修改 0~4 的定时器时间
 
})(i);

}


setTimeout(function() { // 这里增加定时器,超时设置为 5 秒
 
 console.log(new Date, i);

}, 1000 * i);

//0 -> 1 -> 2 -> 3 -> 4 -> 5

还有下面的代码,通过promise来实现。

const tasks = [];
for (var i = 0; i < 5; i++) { // 这里 i 的声明不能改成 let,如果要改该怎么做?
 
 ((j) => {
 
 tasks.push(new Promise((resolve) => {
  
 setTimeout(() => {
  
 console.log(new Date, j);
  
 resolve(); // 这里一定要 resolve,否则代码不会按预期 work
  
}, 1000 * j); // 定时器的超时时间逐步增加
 
}));
 
})(i);

}


Promise.all(tasks).then(() => {
 
 setTimeout(() => {
 
 console.log(new Date, i);
 
}, 1000); // 注意这里只需要把超时设置为 1 秒

});

//0 -> 1 -> 2 -> 3 -> 4 -> 5
const tasks = []; // 这里存放异步操作的 Promise
const output = (i) => new Promise((resolve) => {
 
 setTimeout(() => {
 
 console.log(new Date, i);
 
resolve();
 
}, 1000 * i);

});


// 生成全部的异步操作

for (var i = 0; i < 5; i++) {
 
 tasks.push(output(i));

}


// 异步操作完成之后,输出最后的 i

Promise.all(tasks).then(() => {
 
 setTimeout(() => {
 
 console.log(new Date, i);
 
}, 1000);

});

//0 -> 1 -> 2 -> 3 -> 4 -> 5
// 模拟其他语言中的 sleep,实际上可以是任何异步操作
const sleep = (timeountMS) => new Promise((resolve) => {
 setTimeout(resolve, timeountMS);
});

(async () => { // 声明即执行的 async 函数表达式
 for (var i = 0; i < 5; i++) {
 if (i > 0) {
  await sleep(1000);
 }
 console.log(new Date, i);
 }

 await sleep(1000);
 console.log(new Date, i);
})();
//0 -> 1 -> 2 -> 3 -> 4 -> 5

上面的代码中都用到了闭包,总之,闭包找到的是同一地址中父级函数中对应变量最终的值。

  2.垃圾回收机制 

JavaScript 中的内存管理是自动执行的,而且是不可见的。我们创建基本类型、对象、函数……所有这些都需要内存。

通常用采用的垃圾回收有两种方法:标记清除、引用计数。

1、标记清除

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。

而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。

最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间

2.引用计数

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。

相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,

则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,

它就会释放那些引用次数为0的值所占的内存。

 总结

到此这篇关于js闭包和垃圾回收机制的文章就介绍到这了,更多相关js闭包和垃圾回收内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
游戏人文件夹程序 ver 3.0
Jul 14 Javascript
Javascript学习笔记1 数据类型
Jan 11 Javascript
JSQL 批量图片切换的实现代码
May 05 Javascript
jQuery实现倒计时按钮功能代码分享
Sep 03 Javascript
RequireJS入门一之实现第一个例子
Sep 30 Javascript
JS图片放大效果简单实现代码
Sep 08 Javascript
javascript 利用arguments实现可变长参数
Nov 21 Javascript
vue2.0组件之间传值、通信的多种方式(干货)
Feb 10 Javascript
Vue数据监听方法watch的使用
Mar 28 Javascript
React-router4路由监听的实现
Aug 07 Javascript
npm的lock机制解析
Jun 20 Javascript
Vue绑定用户接口实现代码示例
Nov 04 Javascript
vue前端和Django后端如何查询一定时间段内的数据
Feb 28 #Vue.js
vue-router路由懒加载及实现的3种方式
Feb 28 #Vue.js
vue-router懒加载的3种方式汇总
Feb 28 #Vue.js
Vue SPA 首屏优化方案
Feb 26 #Vue.js
vue 动态添加的路由页面刷新时失效的原因及解决方案
Feb 26 #Vue.js
vue项目配置 webpack-obfuscator 进行代码加密混淆的实现
Feb 26 #Vue.js
vue中h5端打开app(判断是安卓还是苹果)
Feb 26 #Vue.js
You might like
wordpress之wp-settings.php
2007/08/17 PHP
如何批量替换相对地址为绝对地址(利用bat批处理实现)
2013/05/27 PHP
smarty模板引擎中变量及变量修饰器用法实例
2015/01/22 PHP
详解WordPress中分类函数wp_list_categories的使用
2016/01/04 PHP
建立良好体验度的Web注册系统ajax
2007/07/09 Javascript
比较详细的关于javascript 解析json的代码
2009/12/16 Javascript
javascript代码编写需要注意的7个小细节小结
2011/09/21 Javascript
node.js中RPC(远程过程调用)的实现原理介绍
2014/12/05 Javascript
JavaScript如何调试有哪些建议和技巧附五款有用的调试工具
2015/10/28 Javascript
jQuery EasyUI封装简化操作
2016/09/18 Javascript
仿iframe效果Aajx文件上传实例
2016/11/18 Javascript
js自定义QQ菜单效果
2017/01/10 Javascript
js实现鼠标左右移动,图片也跟着移动效果
2017/01/25 Javascript
Nodejs 发送Post请求功能(发短信验证码例子)
2017/02/09 NodeJs
值得收藏的八个常用的js正则表达式
2018/10/19 Javascript
9102年webpack4搭建vue项目的方法步骤
2019/02/20 Javascript
如何在wxml中直接写js代码(wxs)
2019/11/14 Javascript
vue学习笔记之slot插槽用法实例分析
2020/02/29 Javascript
JavaScript运动原理基础知识详解
2020/04/02 Javascript
JS操作JSON常用方法(10w阅读)
2020/12/06 Javascript
[03:07]【DOTA2亚洲邀请赛】我们,梦开始的地方
2017/03/07 DOTA
python中map()与zip()操作方法
2016/02/27 Python
Python中规范定义命名空间的一些建议
2016/06/04 Python
Python可变参数*args和**kwargs用法实例小结
2018/04/27 Python
使用python实现画AR模型时序图
2019/11/20 Python
Python语法之精妙的十个知识点(装B语法)
2020/01/18 Python
浅谈tensorflow中Dataset图片的批量读取及维度的操作详解
2020/01/20 Python
Python faker生成器生成虚拟数据代码实例
2020/07/20 Python
CSS3 优势以及网页设计师如何使用CSS3技术
2009/07/29 HTML / CSS
面包店的创业计划书范文
2014/01/16 职场文书
yy婚礼司仪主持词
2014/03/14 职场文书
特岗教师个人总结
2015/02/10 职场文书
病假条格式范文
2015/08/17 职场文书
2016年“我们的节日·清明节”活动总结
2016/04/01 职场文书
MySQL中使用or、in与union all在查询命令下的效率对比
2021/05/26 MySQL
分享几种python 变量合并方法
2022/03/20 Python