所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称作作用域
)当中。这个变量决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。
变量的执行环境有助于确定应该何时释放内存。
JS是一门具有自动垃圾收集机制的编程语言。离开作用域的值将被自动标记为可以回收,在垃圾收集期间被删除。
当函数内部定义了其它函数时,就创建了闭包。闭包的作用域链包含自己的作用域、包含函数(外部函数)的作用域和全局作用域。通常函数作用域及其所有变量都会在函数执行结束后被销毁,但是当函数返回一个闭包时,这个函数的作用域会一直在内存中保存到闭包不存在为止。
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
f2
可以读取f1
的局部变量,要把f2
作为返回值,可以在f1
外部读取它的内部变量。
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
var add = (function () {
var counter = 0;
return function () {return counter += 1;}
})();
add() //1
add() //2
add() //3
注意: 为什么上面这段代码没有直接写的 function add (){...} 而是把function赋值给了变量add呢?
我们通常会想当然的认为每次调用 add() 都会重走一遍add()中的代码块, 但其实不然。
注意add方法中的return, 它return的并不是1,2,3这样的数值,而是return了一个方法,并且把这个方法赋值给了add变量。
那么在这个function自运行一遍之后,其实最后赋值给add的是return counter += 1 这段代码。
所以后面每次调用add() 其实都是在调用return counter += 1。
再结合之前所说的, 闭包会持有父方法的局部变量并且不会随父方法销毁而销毁, 所以这个counter其实就是来自于第一次function执行时创建的变量。
使用闭包可以模仿块级作用域。
闭包还可以用于在对象中创建私有变量。
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('张三');
p1.setAge(25);
p1.getAge() // 25
创建闭包必须维护额外的作用域,过度使用它们可能会占用大量内存。