作者: 叶茂;
标签: 闭包,内存泄漏

序章
- 词法作用域:作用域是由书写代码时函数声明的位置决定的。
var a = 1
function bar () {
console.log(a)
}
function foo () {
var a = 2
bar()
}
foo() // 1
闭包
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。百度百科
按照广义的解释,下面的代码也可以理解成闭包。
function foo () {
var a = 2
function bar () {
console.log(a)
}
bar()
}
foo()
经典闭包
- 在一个函数中返回一个函数,这是最经典的用来教学闭包的模式。
- 函数在任意地方执行时都能访问其在定义时环境中的变量。
function foo () {
var a = 2
return function () {
console.log(a)
}
}
var bar = foo()
bar()
- 基于javascript的作用域规则,可以将函数中的函数赋值给一个全局变量形成闭包。
var fn;
function foo () {
var a = 2;
function bar () {
console.log(a)
}
fn = bar()
}
foo ()
fn()
(function () {
fn()
}()
- 回调函数,定时器都可以形成闭包。
function setTot (fn, delay) {
console.log(delay + 'ms之后')
fn()
}
function foo () {
var a = 2
setTot(function () {
console.log(a)
}, 2000)
setTimeout(function () {
console.log(a)
}, 2000)
}
foo()
循环和闭包
- 通常人们把这种解决循环问题的方式,叫做使用“闭包”解决循环问题。
for(var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i)
}, 1000)
}
// 定时器中的回调函数是一个在立即执行函数中的闭包,单独的立即执行函数能不能叫做闭包说法各有不同。
for (var i = 0; i < 10; i++) {
(function (j) {
setTimeout(function () {
console.log(j)
}, 1000)
}(i)
}
闭包和内存泄漏
WeakMap。
- WeakMap中的成员是弱引用,变量的引用计数不会将其计算在内,可以用来观察变量是否被回收。
var weakMap = new WeakMap()
function foo () {
var a = new Array(1000).fill('aaa')
var b = new Array(1000).fill('bbb')
weakMap.set(a, 'a')
weakMap.set(b, 'b')
return function () {
console.log(JSON.parse(JSON.stringify(a)))
}
}
var bar = foo()
bar()
setInterval(function () {
console.log(weakMap)
}, 1000)
对象的循环引用
- 在一些博文中可以看到说对象的循环引用,js和dom对象的相互引用会造成内存泄漏,但在实际测试中,chrome中不存在这种现象。
function foo(){
var obj = {a:1}
function c () {
obj.a = 15
}
obj.c = c
}
foo()
function add() {
var el = document.getElementById('el');
el.onclick = function() {
el.style.backgroundColor = 'red';
}
}
add()
总结
- 闭包是基于词法作用域的一种函数使用技巧。
- 闭包的定义说法各有不同,这里也没有给出明确的定义,但大可不必纠结那种定义正确,只要知道闭包的作用以及可能带来的问题即可。
- 许多博文由于年代久远或其他原因,对于闭包和内存泄漏的关系解释并不符合现代引擎的实际结果。
- 闭包会导致局部变量能在函数作用域之外被访问,但是这不是变量不可被回收的充分条件,通常情况下是考虑局部变量是否被全局或者一个不可被回收的对象引用了。