JS的闭合(Closure)
定义
MDN:函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。
JS高程: 闭包是指有权访问另一个函数作用域中的变量的函数。
Javascript权威指南:函数对象可以通过作用域关联起来,函数体内的变量都可以保存在函数作用域内,这在计算机科学文献中称为“闭包”,所有的javascirpt函数都是闭包
你不知道的JS(上):当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前作用域外执行
浏览器原理与实践:当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。
各种书籍上对闭包的定义各不相同,但是我们没必要太过于纠结概念,只要我们深入理解他的含义和使用就可以了
产生原因
闭包产生与如下两个方面有关
- 根据词法作用域的规则,内部作用域可以访问外部作用域
- GC(垃圾回收)机制,如果变量被引用那么GC在回收时并不会回收该变量
实例
function foo() { let test1 = '变量1' const test2 = '变量2' let test3 = '变量3' var innerBar = { getName: function () { console.log(test1) return test2 }, } return innerBar } var bar = foo() bar.getName()
我们可以通过控制台的Source中的scope看到闭包
右边 Scope 项就体现出了作用域链的情况:Local 就是当前的 getName 函数的作用域,Closure(foo) 是指 foo 函数的闭包,最下面的 Global 就是指全局作用域,从“Local–>Closure(foo)–>Global”就是一个完整的作用域链。
注意⚠️:只有我们在内部函数中使用的变量才会被加入闭包(Closure)中。
我们来看下范例中产生闭包时执行栈的情况:
- 当执行到 foo 函数内部的return innerBar这行代码时调用栈的情况
- 当 foo 函数执行完成之后,其整个调用栈的状态(注意⚠️:此时foo执行上下文已出栈)
- 当执行到bar.getName()时的栈状态
通过上面三个图我们可以清晰的看到当前作用域链 是Local–>Closure(foo)–>Global。
用途
实现私有变量
function Animal( ){ //私有变量 var series = "哺乳动物"; function run( ){ console.log("Run!!!"); } //特权方法 this.getSeries = function(){ return series; }; }
实现模块模式
var Counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })(); console.log(Counter.value()); /* logs 0 */ Counter.increment(); Counter.increment(); console.log(Counter.value()); /* logs 2 */ Counter.decrement(); console.log(Counter.value()); /* logs 1 */
缺陷
如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。(如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存)
规避:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。