前言
闭包和垃圾回收机制常常作为前端学习开发中的难点,也经常在面试中遇到这样的问题,本文记录一下在学习工作中关于这方面的笔记。
正文
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的值所占的内存。
总结
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]