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 处理 URL 的两个函数代码
Aug 13 Javascript
js数组去重的常用方法总结
Jan 24 Javascript
JS中的构造函数详细解析
Mar 10 Javascript
jQuery前端框架easyui使用Dialog时bug处理
Dec 05 Javascript
BootStrap入门教程(一)之可视化布局
Sep 19 Javascript
js设置和获取自定义属性的方法
Oct 20 Javascript
微信小程序 页面传参实例详解
Nov 16 Javascript
JQueryEasyUI框架下的combobox的取值和绑定的方法
Jan 22 Javascript
JS加密插件CryptoJS实现的DES加密示例
Aug 16 Javascript
vue自定义指令用法经典实例小结
Mar 16 Javascript
微信小程序实现音乐播放器
Nov 20 Javascript
Vue-cli打包后部署到子目录下的路径问题说明
Sep 02 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实现字符串首字母大写和单词首字母大写的方法
2015/03/14 PHP
PHP中的事务使用实例
2015/05/26 PHP
php实现paypal 授权登录
2015/05/28 PHP
php app支付宝回调(异步通知)详解
2018/07/25 PHP
strpos() 函数判断字符串中是否包含某字符串的方法
2019/01/16 PHP
PHP crypt()函数的用法讲解
2019/02/15 PHP
Yii使用DbTarget实现日志功能的示例代码
2020/07/21 PHP
Node.js node-schedule定时任务隔多少分钟执行一次的方法
2015/02/10 Javascript
Jquery中基本选择器用法实例详解
2015/05/18 Javascript
使用Node.js实现HTTP 206内容分片的教程
2015/06/23 Javascript
jQuery代码性能优化的10种方法
2016/06/21 Javascript
在网页中插入百度地图的步骤详解
2016/12/02 Javascript
bootstrap折叠调用collapse()后data-parent不生效的快速解决办法
2017/02/23 Javascript
Angularjs在360兼容模式下取数据缓存问题的解决办法
2017/06/22 Javascript
AngularJS 中的数据源的循环输出
2017/10/12 Javascript
vue跨域解决方法
2017/10/15 Javascript
Vue中控制v-for循环次数的实现方法
2018/09/26 Javascript
小程序hover-class点击态效果实现
2019/02/26 Javascript
layui实现数据表格隐藏列的示例
2019/10/25 Javascript
微信小程序手动添加收货地址省市区联动
2020/05/18 Javascript
JS+JQuery实现无缝连接轮播图
2020/12/30 jQuery
python实现根据用户输入从电影网站获取影片信息的方法
2015/04/07 Python
Django中的CACHE_BACKEND参数和站点级Cache设置
2015/07/23 Python
python 返回列表中某个值的索引方法
2018/11/07 Python
numpy数组之存取文件的实现示例
2019/05/24 Python
python数据预处理之数据标准化的几种处理方式
2019/07/17 Python
使用tensorflow实现VGG网络,训练mnist数据集方式
2020/05/26 Python
使用django自带的user做外键的方法
2020/11/30 Python
Python实现微信表情包炸群功能
2021/01/28 Python
基于Jquery和Css3代码制作可以缩放的搜索框
2015/11/19 HTML / CSS
外贸业务员求职信范文
2013/12/12 职场文书
先进个人事迹材料
2014/01/25 职场文书
2014年迎新年活动方案
2014/02/19 职场文书
小学生读书笔记范文
2015/06/30 职场文书
详解Nginx启动失败的几种错误处理
2021/04/01 Servers
MySQL REVOKE实现删除用户权限
2021/06/18 MySQL