摘要:闭包的问题使用闭包会将局部变量保持在内存中,所以会占用大量内存,影响性能。当闭包的作用域中保存一些节点时,较容易出现循环引用,可能会造成内存泄漏。
前言
秋招大大小小若干场面试,几乎都问了这个问题,当然不知道闭包也不能说会js。所以,不要逃避,好好地来梳理一下。
闭包是什么?来看看MDN官网上关于闭包的定义:
A closure is the combination of a function and the lexical environment within which that function was declared.
闭包是函数以及函数声明所在的词法环境的组合。这个定义有点干涩,个人认为可以结合闭包的实际场景简单地这么理解:函数调用结果返回一个子函数,同时该子函数使用了外部函数的局部变量,使得变量保存在内存中,形成了闭包。
举个例子?举个最简单的栗子~
function func(){ var count = 0; return function(){ console.log(count++); } } var fn = func(); fn(); // 0 fn(); // 1
func 函数执行返回一个函数,其中调用了func的局部变量count,导致func函数执行完成后,count变量仍保留在内存空间,未被销毁,形成了闭包。
闭包的作用?从上一个例子,可以看到闭包的特点是读取函数内部局部变量,并将局部变量保存在内存,延长其生命周期。利用这个特点可以使用闭包实现以下功能:
解决类似循环绑定事件的问题
在实际开发中,经常会遇到需要循环绑定事件的需求,比如上一篇博客的例子,在id为container的元素中添加5个按钮,每个按钮的文案是相应序号,点击打印输出对应序号。
其中第一个方法很容易错误写成:
var container = document.getElementById("container"); for(var i = 1; i <= 5; i++) { var btn = document.createElement("button"), text = document.createTextNode(i); btn.appendChild(text); btn.addEventListener("click", function(){ console.log(i); }) container.appendChild(btn); }
虽然给不同的按钮分别绑定了事件函数,但是5个函数其实共享了一个变量 i。由于点击事件在 js 代码执行完成之后发生,此时的变量 i 值为6,所以每个按钮点击打印输出都是6。
为了解决这个问题,我们可以修改代码,给各个点击事件函数建立独立的闭包,保持不同状态的i。
var container = document.getElementById("container"); for(var i = 1; i <= 5; i++) { (function(_i) { var btn = document.createElement("button"), text = document.createTextNode(_i); btn.appendChild(text); btn.addEventListener("click", function(){ console.log(_i); }) container.appendChild(btn); })(i); }
注:解决这个问题更好的方法是使用 ES6 的 let,声明块级的局部变量。
封装私有变量
经典的计数器例子:
function makeCounter() { var value = 0; return { getValue: function() { return value; }, increment: function() { value++; }, decrement: function() { value--; } } } var a = makeCounter(); var b = makeCounter(); b.increment(); b.increment(); b.decrement(); b.getValue(); // 1 a.getValue(); // 0 a.value; // undefined
每次调用makeCounter函数,环境是不相同的,所以对b进行的increment/decrement操作不会影响a的value属性。同时,对value属性,只能通过getValue方法进行访问,而不能直接通过value属性进行访问。
闭包的问题?使用闭包会将局部变量保持在内存中,所以会占用大量内存,影响性能。所以在不再需要使用这些局部变量的时候,应该手动将这些变量设置为null, 使变量能被回收。
当闭包的作用域中保存一些DOM节点时,较容易出现循环引用,可能会造成内存泄漏。原因是在IE9以下的浏览器中,由于BOM 和DOM中的对象是使用C++以COM 对象的方式实现的,而COM对象的垃圾收集机制采用的是引用计数策略,当出现循环引用时,会导致对象无法被回收。当然,同样可以通过设置变量为null解决。
举例如下:
function func() { var element = document.getElementById("test"); element.onClick = function() { console.log(element.id); }; }
func 函数为 element 添加了闭包点击事件,匿名函数中又对element进行了引用,使得 element 的引用始终不为0。解决办法是使用变量保存所需内容,并在退出函数时将 element 置为 null。
function func() { var element = document.getElementById("test"), id = element.id; element.onClick = function() { console.log(id); }; element = null; }参考文章
闭包
JavaScript设计模式与开发实践
闭包会造成内存泄漏吗?
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/89136.html
摘要:建筑的顶层代表全局作用域。实际的块级作用域远不止如此块级作用域函数作用域早期盛行的立即执行函数就是为了形成块级作用域,不污染全局。这便是闭包的特点吧经典面试题下面的代码输出内容答案个如何处理能够输出闭包方式方式下一篇你不知道的笔记 下一篇:《你不知道的javascript》笔记_this 写在前面 这一系列的笔记是在《javascript高级程序设计》读书笔记系列的升华版本,旨在将零碎...
摘要:但是,必须强调,闭包是一个运行期概念。通过原型链可以实现继承,而与闭包相关的就是作用域链。常理来说,一个函数执行完毕,其执行环境的作用域链会被销毁。所以此时,的作用域链虽然销毁了,但是其活动对象仍在内存中。 学习Javascript闭包(Closure)javascript的闭包JavaScript 闭包深入理解(closure)理解 Javascript 的闭包JavaScript ...
摘要:闭包是怎么通过作用域链霸占更多内存的本文是作者学习高级程序设计第一小节的一点个人理解,详细教程请参考原教材。函数执行过程创建了一个函数的活动对象,作用域链的最前端指向这个对象。函数执行完毕返回值后执行环境作用域链和活动对象一并销毁。 JavaScript 闭包是怎么通过作用域链霸占更多内存的? 本文是作者学习《JavaScript 高级程序设计》7.2第一小节的一点个人理解,详细教程请...
摘要:一前言这个周末,注意力都在学习基础知识上面,刚好看到了闭包这个神圣的东西,所以打算把这两天学到的总结下来,算是巩固自己所学。因此要注意闭包的使用,否则会导致性能问题。五总结闭包的作用能够读取其他函数内部变量。 # 一、前言 这个周末,注意力都在学习基础Js知识上面,刚好看到了闭包这个神圣的东西,所以打算把这两天学到的总结下来,算是巩固自己所学。也可能有些不正确的地方,也请大家看到了,麻...
摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。异步编程入门的全称是前端经典面试题从输入到页面加载发生了什么这是一篇开发的科普类文章,涉及到优化等多个方面。 TypeScript 入门教程 从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript。 网络基础知识之 HTTP 协议 详细介绍 HTT...
阅读 3394·2023-04-25 22:44
阅读 859·2021-11-15 11:37
阅读 1601·2019-08-30 15:55
阅读 2582·2019-08-30 15:54
阅读 1046·2019-08-30 13:45
阅读 1401·2019-08-29 17:14
阅读 1806·2019-08-29 13:50
阅读 3345·2019-08-26 11:39