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 相关文章推荐
JS BASE64编码 window.atob(), window.btoa()
Mar 09 Javascript
使用SyntaxHighlighter实现HTML高亮显示代码的方法
Feb 04 Javascript
web的各种前端打印方法之jquery打印插件jqprint实现网页打印
Jan 09 Javascript
js生成的验证码的实现与技术分析
Sep 17 Javascript
javascript面向对象程序设计(一)
Jan 29 Javascript
JS使用post提交的两种方式
Dec 03 Javascript
详解AngularJS 模态对话框
Apr 07 Javascript
省市联动效果的简单实现代码(推荐)
Jun 06 Javascript
详细介绍RxJS在Angular中的应用
Sep 23 Javascript
Vue Router去掉url中默认的锚点#
Aug 01 Javascript
ios设备中angularjs无法改变页面title的解决方法
Sep 13 Javascript
JS获取当前时间的实例代码(昨天、今天、明天)
Nov 13 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统计字符串中中英文字符的个数
2013/06/23 PHP
php读取目录及子目录下所有文件名的方法
2014/10/20 PHP
php下Memcached入门实例解析
2015/01/05 PHP
php获取字符串中各个字符出现次数的方法
2015/02/23 PHP
Laravel框架源码解析之入口文件原理分析
2020/05/14 PHP
使用Modello编写JavaScript类
2006/12/22 Javascript
索趣科技的答案
2007/02/07 Javascript
Ext 今日学习总结
2010/09/19 Javascript
简单漂亮的js弹窗可自由拖拽且兼容大部分浏览器
2013/10/22 Javascript
开发插件的两个方法jquery.fn.extend与jquery.extend
2013/11/21 Javascript
JQuery中serialize() 序列化
2015/03/13 Javascript
Javascript中的call()方法介绍
2015/03/15 Javascript
javascript框架设计之类工厂
2015/06/23 Javascript
JavaScript数据绑定实现一个简单的 MVVM 库
2016/04/08 Javascript
微信小程序实战之自定义toast(6)
2017/04/18 Javascript
微信小程序图片选择、上传到服务器、预览(PHP)实现实例
2017/05/11 Javascript
JavaScript实现开关等效果
2017/09/08 Javascript
elementui的默认样式修改方法
2018/02/23 Javascript
vue-prop父组件向子组件进行传值的方法
2018/03/01 Javascript
vue spa应用中的路由缓存问题与解决方案
2019/05/31 Javascript
JS三级联动代码格式实例详解
2019/12/30 Javascript
详解JavaScript中的Object.is()与&quot;===&quot;运算符总结
2020/06/17 Javascript
Python利用pyHook实现监听用户鼠标与键盘事件
2014/08/21 Python
在pandas中一次性删除dataframe的多个列方法
2018/04/10 Python
Python爬取个人微信朋友信息操作示例
2018/08/03 Python
解决python有时候import不了当前的包问题
2019/08/28 Python
记录模型训练时loss值的变化情况
2020/06/16 Python
Solid & Striped官网:美国泳装品牌
2019/06/19 全球购物
英国家具、照明、家居用品网上商店:Wayfair.co.uk
2020/02/13 全球购物
商铺租赁意向书
2014/04/01 职场文书
汽车技术服务与贸易专业求职信
2014/07/20 职场文书
2014乡镇党政班子四风问题思想汇报
2014/09/14 职场文书
2016年社区六一儿童节活动总结
2016/04/06 职场文书
判断Python中的Nonetype类型
2021/05/25 Python
Go语言实现Snowflake雪花算法
2021/06/08 Golang
一文彻底理解js原生语法prototype,__proto__和constructor
2021/10/24 Javascript