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 相关文章推荐
javascript:void(0)使用探讨
Aug 27 Javascript
jquery live()重复绑定的解决方法介绍
Jan 03 Javascript
js判断设备是否为PC并调整图片大小
Feb 12 Javascript
js中的onchange和onpropertychange (onchange无效的解决方法)
Mar 08 Javascript
JS实现的不规则TAB选项卡效果代码
Sep 18 Javascript
微信小程序 地图map详解及简单实例
Jan 10 Javascript
HTML5+Canvas调用手机拍照功能实现图片上传(上)
Apr 21 Javascript
jquery实现倒计时小应用
Sep 19 jQuery
微信小程序wx.uploadfile 本地文件转base64的实现代码
Jun 28 Javascript
npm scripts 使用指南详解
Oct 08 Javascript
Vue实现类似Spring官网图片滑动效果方法
Mar 01 Javascript
为什么node.js不适合大型项目
Apr 28 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
php分页代码学习示例分享
2014/02/20 PHP
PHP加Nginx实现动态裁剪图片方案
2014/03/10 PHP
关于PHP的curl开启问题探讨
2014/04/08 PHP
php+mysqli批量查询多张表数据的方法
2015/01/29 PHP
ThinkPHP文件缓存类代码分享
2015/04/22 PHP
如何利用预加载优化Laravel Model查询详解
2017/08/11 PHP
js将long日期格式转换为标准日期格式实现思路
2013/04/07 Javascript
Javascript/Jquery——简单定时器的多种实现方法
2013/07/03 Javascript
使用javascript实现ListBox左右全选,单选,多选,全请
2013/11/07 Javascript
JS获取DropDownList的value值与text值的示例代码
2014/01/07 Javascript
jQuery中的jQuery()方法用法分析
2014/12/27 Javascript
javascript数组克隆简单实现方法
2015/12/16 Javascript
基于jquery fly插件实现加入购物车抛物线动画效果
2016/04/05 Javascript
全面解析Bootstrap中Carousel轮播的使用方法
2016/06/13 Javascript
nodejs结合Socket.IO实现的即时通讯功能详解
2018/01/12 NodeJs
KOA+egg.js集成kafka消息队列的示例
2018/11/09 Javascript
layui导出所有数据的例子
2019/09/10 Javascript
html+vue.js 实现漂亮分页功能可兼容IE
2020/11/07 Javascript
Python Web框架Flask中使用七牛云存储实例
2015/02/08 Python
详解python列表生成式和列表生成式器区别
2019/03/27 Python
python按修改时间顺序排列文件的实例代码
2019/07/25 Python
pytorch torch.expand和torch.repeat的区别详解
2019/11/05 Python
Python安装whl文件过程图解
2020/02/18 Python
雅诗兰黛美国官网:Estee Lauder美国
2016/07/21 全球购物
肯尼亚网上商城:Kilimall
2016/08/20 全球购物
Marriott中国:万豪国际酒店查询预订
2016/09/02 全球购物
ziaja齐叶雅官方海外旗舰店:来自波兰的天然护肤品牌
2017/01/02 全球购物
全球最大的游戏市场:G2A
2018/07/05 全球购物
乌克兰在线电子产品商店:MTA
2019/11/14 全球购物
德国50岁以上交友网站:Lebensfreunde
2020/03/18 全球购物
干部鉴定材料
2014/05/18 职场文书
合作与交流自我评价
2015/03/09 职场文书
土木工程毕业答辩开场白
2015/05/29 职场文书
堂吉诃德读书笔记
2015/06/30 职场文书
为Centos安装指定版本的Docker
2022/04/01 Servers
Android移动应用开发指南之六种布局详解
2022/09/23 Java/Android