简介:函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局环境下可访问,就形成了闭包。
可前往公众号查看原文:
前端面试,关注公众号,查看最新面经和技术文章哦~
闭包并不是 JavaScript 特有的概念,社区上对于闭包的定义也并不完全相同。虽然本质上表达的意思相似,但是晦涩且多样的定义仍然给初学者带来了困惑。我自己认为比较容易理解的闭包定义为:函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局环境下可访问,就形成了闭包。
function numGenerator() {
let num = 1;
num++;
return () => {
console.log(num);
};
}
var getNum = numGenerator();
getNum();
这个简单的闭包例子中,numGenerator 创建了一个变量 num,返回打印 num 值的匿名函数,这个函数引用了变量 num,使得外部可以通过调用 getNum 方法访问到变量 num,因此在 numGenerator 执行完毕后,即相关调用栈出栈后,变量 num 不会消失,仍然有机会被外界访问。
内存管理
内存管理是计算机科学中的概念。不论是什么程序语言,内存管理都是指对内存生命周期的管理,而内存的生命周期无外乎:var foo = 'bar' // 在堆内存中给变量分配空间
alert(foo) // 使用内存
foo = null // 释放内存空间
内存管理基本概念
- 栈空间:由操作系统自动分配释放,存放函数的参数值,局部变量的值等,其操作方式类似于数据结构中的栈。
- 堆空间:一般由开发者分配释放,这部分空间就要考虑垃圾回收的问题。
在 JavaScript 中,数据类型包括(未包含 ES Next 新数据类型):- 基本数据类型,如 Undefined、Null、Number、Boolean、String 等
- 引用类型,如 Object、Array、Function 等
一般情况下,基本数据类型保存在栈内存当中,引用类型保存在堆内存当中。如下代码:var a = 11
var b = 10
var c = [1, 2, 3]
var d = { e: 20 }
对于分配内存和读写内存的行为所有语言都较为一致,但释放内存空间在不同语言之间有差异。例如,JavaScript 依赖宿主浏览器的垃圾回收机制,一般情况下不用程序员操心。但这并不表示万事大吉,某些情况下依然会出现内存泄漏现象。内存泄漏是指内存空间明明已经不再被使用,但由于某种原因并没有被释放的现象。这是一个非常「玄学」的概念,因为内存空间是否还在使用,某种程度上是不可判定问题,或者判定成本很高。内存泄漏危害却非常直观:它会直接导致程序运行缓慢,甚至崩溃。内存泄漏场景举例
我们来看几个典型引起内存泄漏的例子:var element = document.getElementById("element")
element.mark = "marked"
// 移除 element 节点
function remove() {
element.parentNode.removeChild(element)
}
上面的代码,我们只是把 id 为 element 的节点移除,但是变量 element 依然存在,该节点占有的内存无法被释放。我们需要在 remove 方法中添加:element = null,这样更为稳妥。var element = document.getElementById('element')
element.innerHTML = '点击'
var button = document.getElementById('button')
button.addEventListener('click', function() {
// ...
})
element.innerHTML = ''
这段代码执行后,因为 element.innerHTML = '',button 元素已经从 DOM 中移除了,但是由于其事件处理句柄还在,所以依然无法被垃圾回收。我们还需要增加 removeEventListener,防止内存泄漏。
function foo() {
var name = 'lucas'
window.setInterval(function() {
console.log(name)
}, 1000)
}
foo()
浏览器垃圾回收
当然,除了开发者主动保证以外,大部分的场景浏览器都会依靠:内存泄漏和垃圾回收注意事项
关于内存泄漏和垃圾回收,要在实战中分析,不能完全停留在理论层面,毕竟如今浏览器千变万化且一直在演进当中。从以上示例我们可以看出,借助闭包来绑定数据变量,可以保护这些数据变量的内存块在闭包存活时,始终不被垃圾回收机制回收。因此,闭包使用不当,极可能引发内存泄漏,需要格外注意。以下代码:function foo() {
let value = 123
function bar() { alert(value) }
return bar
}
let bar = foo()
这种情况下,变量 value 将会保存在内存中,如果加上:这样的话,随着 bar 不再被引用,value 也会被清除。结合浏览器引擎的优化情况,我们对上述代码进行改动:function foo() {
let value = Math.random()
function bar() {
debugger
}
return bar
}
let bar = foo()
bar()
在 Chrome 浏览器 V8 最新引擎中,执行上述代码。我们在函数 bar 中打断点,会发现 value 没有被引用,如下图:而我们在 bar 函数中加入对 value 的引用:function foo() {
let value = Math.random()
function bar() {
console.log(value)
debugger
}
return bar
}
let bar = foo()
bar()
会发现此时引擎中存在闭包变量 value 值。如下图: