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 相关文章推荐
jQuery 性能优化指南(3)
May 21 Javascript
Javascript 页面模板化很多人没有使用过的方法
Jun 05 Javascript
js优化针对IE6.0起作用(详细整理)
Dec 25 Javascript
通过正则格式化url查询字符串实现代码
Dec 28 Javascript
jQuery实现防止提交按钮被双击的方法
Mar 24 Javascript
javascript实现页面刷新时自动清空表单并选中的方法
Jul 18 Javascript
Bootstrap免费字体和图标网站(值得收藏)
Mar 16 Javascript
jQuery实现Select下拉列表进行状态选择功能
Mar 30 jQuery
客户端(vue框架)与服务器(koa框架)通信及服务器跨域配置详解
Aug 26 Javascript
微信小程序实现多选框全选与取消全选功能示例
May 14 Javascript
浅谈Vue SSR中的Bundle的具有使用
Nov 21 Javascript
vue 项目软键盘回车触发搜索事件
Sep 09 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
基于文本的搜索
2006/10/09 PHP
PHP OPP机制和模式简介(抽象类、接口和契约式编程)
2014/06/09 PHP
php实现cookie加密的方法
2015/03/10 PHP
PHP生成器简单实例
2015/05/13 PHP
PHP面向对象详解(三)
2015/12/07 PHP
php微信公众号开发(4)php实现自定义关键字回复
2016/12/15 PHP
利用PHP判断是手机移动端还是PC端访问的函数示例
2017/12/14 PHP
laravel框架添加数据,显示数据,返回成功值的方法
2019/10/11 PHP
漂亮的提示信息(带箭头)
2007/03/21 Javascript
为超链接加上disabled后的故事
2010/12/10 Javascript
判断滚动条到底部的JS代码
2013/11/04 Javascript
12种不宜使用的Javascript语法整理
2013/11/04 Javascript
js时间比较示例分享(日期比较)
2014/03/05 Javascript
现代 JavaScript 开发编程风格Idiomatic.js指南中文版
2014/05/28 Javascript
jquery $(document).ready()和window.onload的区别浅析
2015/02/04 Javascript
jQuery简单实现禁用右键菜单
2015/03/10 Javascript
图解js图片轮播效果
2015/12/20 Javascript
第一篇初识bootstrap
2016/06/21 Javascript
input框中的name和id的区别
2016/11/16 Javascript
浅谈Node.js:理解stream
2016/12/08 Javascript
Javascript中将变量转换为字符串的三种方法
2017/09/19 Javascript
vue实现底部菜单功能
2018/07/24 Javascript
Vue-CLI3.x 设置反向代理的方法
2018/12/06 Javascript
Vue基于vuex、axios拦截器实现loading效果及axios的安装配置
2019/04/26 Javascript
vue下载excel的实现代码后台用post方法
2019/05/10 Javascript
layui监听工具栏的实例(操作列表按钮)
2019/09/10 Javascript
JavaScript Html实现移动端红包雨功能页面
2021/01/10 Javascript
python调用staf自动化框架的方法
2018/12/26 Python
Python第三方库h5py_读取mat文件并显示值的方法
2019/02/08 Python
不到20行代码用Python做一个智能聊天机器人
2019/04/19 Python
Python利用matplotlib做图中图及次坐标轴的实例
2019/07/08 Python
Python列表(list)所有元素的同一操作解析
2019/08/01 Python
物理教育专业毕业生推荐信
2013/11/03 职场文书
统计员岗位职责
2015/02/11 职场文书
2015年重阳节主持词
2015/07/04 职场文书
关于幸福的感言
2015/08/03 职场文书