预备知识:
变量
- 全局变量
- 局部变量
Javascript语言特点:- 函数内部可以读取外部的全局变量
- 函数外部无法读取内部变量
var n = 1;
var foo = function() {
console.log(n);
}
foo(); //1
Javascript的变量声明
闭包是什么
ECMAScript 中给闭包的定义是:闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。
有点晦涩,那就举例子。
var foo1 = function () {
var n = 999;
function foo2() {
console.log(n);
}
return foo2;
}
var result=foo1();
result(); // 999
正常情况下,在foo1外部是无法获取到foo1函数内部的局部变量的,因为var n 的作用域是foo1的函数作用域。但是通过在foo1中将foo2作为return出来,即可获取foo1的内部函数foo2,而对于foo2来说,var n是可见的,所以在foo2中操作n是可行的。所以,即实现了,在foo1的外部,通过foo2这个桥,实现了操作foo1内部变量的功能
小结(什么是闭包)
- 闭包是一个函数,满足以下条件
- 闭包使用其他函数定义的变量,使其不被销毁。如foo2操作n,使n不被回收
- 闭包存在定义该变量的作用域中,两者是同一个作用域。如foo2和n是同一个作用域。
闭包为什么(存在意义)
一般有两个作用:
- 函数外部获取函数内部变量的值(类比对象的public method&private variable)
- 内存保持,不被垃圾回收机制释放。如在上图中,result不被释放,则foo1的函数作用域中的变量被全部保存在内存当中。
var result = foo1();
闭包怎么用
正常用法
没啥说的,抛代码吧。
eg1.
var foo1 = function () {
var n = 999;
function foo2() {
console.log(n);
}
return foo2;
}
var result=foo1();
result(); // 999
eg2.
for (var i = 0; i < 4; i++) {
setTimeout(function () {
console.log(i)
}, 0)
}
// 4 4 4 4
参考《let VS var》中对var声明的全局变量部分对这段代码的分析。而通过闭包如下,在不适用let声明i的情况下实现预期的0-3的打印。
for (var i = 0; i < 4; i++) {
(function (i) {
setTimeout(function () {
console.log(i)
}, 0)
})(i)
} //0 1 2 3
setTimeout即成为了一个闭包,并被迭代调用4次,然后放在了loop的队列尾。每次的i都被setTimeout保持在内存中不被释放,直到console.log结束。
重点关注(可能导致的异常)
- 内存管理
- 正如闭包的作用,保持内存,这可能造成内存管理的混乱。导致局部变量无法被正常释放而一直像全局变量一样被占用内存。如果需要回收这些变量占用的内存,可以手动将变量设置为null。
- DOM对象和JavaScript对象的循环依赖导致内存无法释放
问题代码:
function func() {
var test = document.getElementById('test');
test.onclick = function () {
console.log('hello world');
}
}
问题分析
两个对象:JavaScript对象test,DOM对象简称为'testId'
在func方法中,test引用了DOM对象testId, 然后以test为中转,给testId这样一个DOM对象的onclick事件绑定了一个闭包。如果在被绑定闭包中,访问test这个JavaScript对象。
那么,就形成了互相依赖:
- test依赖testId(引用)
- testId(的点击事件handler)依赖test(在绑定事件中的handler中操作了test)
循环依赖的结果导致,这两个对象都没办法被释放。
解决方案1:
function func() {
var test = document.getElementById('test');
test.onclick = function () {
console.log('hello world');
}
test = null;
}
解决思路:干掉作为“中转变量”只是为了绑定click handler的test,打破循环。(类比死锁解决方案中的检测与解除--剥夺)
解决方案2:
function func() {
var test = document.getElementById('test');
test.onclick = testClickHandler()
}
var testClickHandler = function() {
console.log('hello world');
}
解决思路:打破闭包,使click的handler无法再满足闭包条件,来访问func内部变量,从根源(闭包的产生的三条件)上杜绝相互依赖的发生。(类比死锁解决方案中的预防--破坏形成死锁的先决条件)
补充
- 待补充模块:
- JavaScript Garbage Collection
- JavaScript->this, self
- JavaScript变量的声明、赋值、和引用(地址)
- JavaScript的深浅拷贝
- Refs:
网友评论