闭包:能够访问另一个函数作用域的变量的函数;是函数和声明该函数的词法环境的组合
闭包的形成与函数作用域链有关
function outer() {
var a = 2;
var inner = function() {
console.log(a)
}
return inner // inner就是一个闭包,因为它能够访问outer函数的作用域的变量
}
很多人会搞不懂匿名函数与闭包的关系,实际上,闭包是站在作用域的角度上来定义的,因为inner访问到outer作用域的变量,所以inner就是一个闭包函数。
闭包所需要注意的地方:this指向,变量的作用域,会造成内存泄露
思考:为什么闭包函数能够访问其他函数的作用域?
从堆栈的角度看待js函数
基本变量的值一般都是存在栈内存中,对象类型的变量的值存储在堆内存中,栈内存存储对应空间地址。基本类型的数据:Number、Boolean、Undefined、String、Null
var a = 1;
var b = {m: 20}
对应内存存储
当我们执行 b={m: 30}
时,堆内存就用新对象{m:30}
,栈内存的b指向新的空间地址,而堆内存中原来的{m: 20}就会把程序引擎垃圾回收掉
var a = 3;
function fn() {
var b = 4;
function fn1() {
console.log(b)
}
fn1()
}
fn()
栈:一种先进后出的数据结构
- 在执行fn前,此时我们在全局执行环境,全局作用域里有变量a,对象fn
- 进入fn,此时栈内存就会push一个fn的执行环境,这个环境里有变量b和对象fn1,在这里可以访问到自身执行环境和全局执行环境
- 进入fn1,会push一个fn1的执行环境到执行栈,这里没有定义其他变量,但是这里也同样可以访问到fn的执行环境和全局执行环境,因为当程序在访问变量时,会向底层一个个栈查找,如果在全局执行环境里找不到,则会报undefined错误
- 随着fn1()执行完毕,fn1的执行环境被销毁,接着执行完fn(),fn的执行环境也会被销毁,只剩下全局执行环境。现在没有变量b和fn1函数对象了,只有a和fn
当程序调用某个函数时,做了以下工作:准备执行环境,初始化函数作用域链和arguments参数对象
现在我们会看最初的例子outer与inner
function outer() {
var a = '1'
var inner = function() {
console.log(a)
}
return inner;
}
var inner = outer()
inner()
当程序执行完var inner = outer()
,其实outer的执行环境并没有被销毁,因为他里面的变量a仍然被被inner的函数作用域链所引用,当程序执行完inner(), 这时候,inner和outer的执行环境才会被销毁调;
坑点1:应用的变量可能发生变化
function outer() {
var result = [];
for(var i = 0; i < 10; i++) {
result.[i] = function() {
console.log(i)
}
}
return result // 只有打印的结果全部是10
}
改成如下
function outer() {
var result = []
for(var i = 0; i < 10; i++) {
result.[i] = function(num) {
return function() {
console.log(num)
}
}(i)
}
}
坑点2:this指向问题
var object = {
name: 'object',
getName: function() {
return function() {
console.log(this.name)
}
}
}
object.getName()() // undefined
// 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向windows
坑点3:内存泄露问题
function showId() {
var el = document.getElementById('id')
el.onclick = function() {
console.log(el.id) // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
}
}
改成下面
fucntion showId() {
var el = document.getElementById('id')
var id = el.id
el.onclick = function() {
console.log(id)
}
el = null // 主动释放
}
用闭包模仿块级作用域
(function() {
for(var i = 0; i< 10; i++) {
console.log(i)
}
})()
console.log(i) // undefined 因为i随着闭包函数的退出,执行环境销毁,变量回收
有意思的示例
function makeAdd(x) {
return function(y) {
return x + y
}
}
var add5 = makeAdd(5)
var add10 = makeAdd(10)
console.log(add5(2)) // 7
console.log(add10(2)) // 12
add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val
}
return {
increment: function() {
changeBy(1)
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})()
Counter.increment()
Counter.increment()
console.log(Counter.value())
常见闭包形式
function v() {
setTimeout(function() {
// 闭包函数
}, 0)
document.getElementById('btn').addEventListener('click', function() {
// 闭包函数
})
$.ajax({
url: '',
type: 'post',
success: function() {
// 闭包函数
}
})
return {
n: function() {
// 闭包函数
}
n: function() {
// 闭包函数
}
}
}
v()