概念: 有权访问另外一个函数做用域中的变量的函数 优势: 内存驻留、避免全局变量污染 缺点: 内存泄漏(?)、没法预知变量被更改 相关知识点: 做用域、内存驻留、内存泄露、JS执行机制、内存机制、垃圾回收机制
要理解什么是闭包、首先咱们要对JS中的做用域
和做用域链
有必定的理解:前端
做用域: 简单来说就是一个变量可以被访问的范围,JS有三种做用域,分别是:全局做用域、函数做用域、块级做用域 做用域链: 在JS中做用域是一层一层嵌套的, 子做用域中能够访问父做用域中的变量, 咱们把这种能够一层一层向上访问的链式结构叫作做用域链.
~~ 当JS建立一个函数时, 首先会建立一个预先包含全局变量对象的做用域链
, 保存在内部的Scope
属性之中
当JS调用这个函数时, 会为此函数建立一个执行环境
, 也就是函数上下文
而后复制函数的Scope
的属性中的对象构建执行环境的做用域链
~~浏览器
做用域与做用域链就比如是数学中的集合, 最大的即是全局做用域, 子集即是函数做用域, 子集中又能够有子集,全部的本身均可以向外访问,但全部的父级不能够向子集访问,相同的子集之间也不能够互相访问。安全
有权访问另外一个函数做用域中的变量的函数 《JavaScript高级程序设计》闭包
咱们来理解一下这句话异步
那么怎么才有权访问另外一个函数做用域中的变量呢?
根据上文中子做用域中能够访问父做用域中的变量
的特性,答案是: 成为另外一个函数的子函数函数
在《JavaScript权威指南》, 强调了函数体内部变量能够保存在函数做用域 函数对象能够经过做用域链相互关联起来,函数体内部变量能够保存在函数做用域内,这就是闭包。
性能
从严格的角度来说, 闭包须要知足三个必要的条件:学习
所以咱们猜测一个闭包的样子, 大概应该是这样的:线程
// 全局变量-全局做用域 var global = "global scope"; function partner() { // 局部变量-函数做用域 var variable = 'Function scope'; function children() { console.log(variable); } } // 此时子函数 children 访问了父函数 partner, 咱们就称子函数 children 为闭包.
意义: 内存驻留
当咱们要实现一个计数器时, 首先用常规的方法来写:设计
// 计数器 var count = 0; function counter() { console.log(count++); } counter(); // 1 counter(); // 2
上面的代码已经实现了咱们所需的功能, 可是它并不完美, 一方面全局的count
变量可能形成变量污染, 另外一方面代码中的任何一个位置均可以轻松的修改这个count的值, 这是咱们所不能接受的!
所以, 咱们须要一个变量能够在counter
函数中访问, 但它并不在全局做用域中, 且能够长时间的停留在内存当中不被浏览器的垃圾回收机制
清除, 因而咱们就想到了闭包, 接下来咱们用闭包再实现一下计数器:
// 计数器 var counter = (function() { var count = 0; return function() { console.log(count++); } })() counter(); // 1 counter(); // 2
闭包仿佛结合了全局做用域与局部做用域的优势与一身,对于其余函数做用域而言,父函数做用域中的变量就像是父函数和闭包的一个 “私有变量” , 而对于父函数和闭包而言, 父函数做用域中的变量又好像身处 “全局做用域” 中.
缺陷: 影响性能、变量修改
闭包的存在会致使函数中得变量一直存在于内存中,不能被垃圾回收机制清理, 致使内存消耗增长, 影响系统运行的性能,因此不能滥用闭包.
若是要使用闭包, 应该在使用结束时手动的清除闭包!
在闭包存在的时候,将父函数比作一个类,父函数中得局部变量就是类的私有属性,而闭包访问的变量就是类的公共属性,在父函数做用域和闭包函数中均可以对 variable 变量进行修改, 这是件使人头疼的事情, 由于你并不知道有多少闭包会在何时对你的变量进行修改, 这将形成你的程序极不稳定甚至执行异常.
function parent() { let name = 'sf' return { get() { return name }, set(val) { name = val } } } const nameProxy = parent() console.log(nameProxy.get()) // 'sf' // 子函数children也能够定义为null在全局,而后在parent中赋值
// 由于setTimeout是异步的, 代码执行先同步后异步, 因此当它执行的时候for循环已经结束了 for(var i=0,len=10; i<len; i++) { setTimeout(() => console.log(i), 1); } // 10个10 // 闭包写法-IIFE for(var i=0,len=10; i<len; i++) { ((i) => { setTimeout(() => console.log(i), 1); })(i) }
// 内部的变量不会污染全局变量, 能够放心的对多个模块进行合并 (function(window) { let a = 1 let b = '2' function add() { a++ } })(window)
执行父函数时, JS线程会对内部的子函数进行预编译, 看一看子函数中是否用到了父函数的内部变量
若是用到了, 为了保证在将来调用子函数时不出错, JS线程会在父函数执行完毕以后, 清空函数执行栈中的上下文以前, 将父函数中被用到的变量 copy 一份放在堆中, 供以后子函数引用.
内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。
在闭包形成的影响中,咱们常常会听到一句话, 那即是:在IE中闭包的使用可能会致使内存泄漏。可是,在我学习V8引擎的过程当中发现,引起内存泄漏的缘由彷佛是循环引用,这让我对闭包和内存泄漏的关系产生了疑惑.
后续我将围绕内存泄漏
从新整理一篇文章,这里先引用一篇文章中的一句话和一个例子:
在IE浏览器中,因为BOM和DOM中的对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,若是两个对象之间造成了循环引用,那么这两个对象都没法被回收,但循环引用形成的内存泄露在本质上也不是闭包形成的。 做者:前端小学生\_f675 连接:https://www.jianshu.com/p/66881ba3c8ba 来源:简书 著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。
// 内存泄漏 var ele = document.getElementById("someElement"); ele.click = function() { console.log(ele.id); } // 解决方案:使用结束后释放内存 var ele = document.getElementById("someElement"); var eleID = ele.id; ele.click = function() { console.log(eleID); } ele = null;
文章地址: 待更新