自由变量
自由变量:能够在函数中使用,但既不是函数的参数,也不是局部变量的那些变量
所有的自由变量都应该在函数定义的地方,向上级作用域寻找,不是在执行的地方。
闭包和自由变量
闭包:能够访问自由变量的函数
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope
}
return f//返回函数的声明
}
var foo = checkscope();
foo();//静态local scope,动态global scope
globalContext = {
VO:{//变量对象
scope:undefined(声明)/gloabl scope(执行),
checkscope:reference to function checkscope,
foo:undefined(声明)/函数执行,
},
scope:[globalContext.VO]//作用域链
}
checkScopeContext = {
VO/AO:{//声明时VO,执行后变成AO
scope:undefined/gloabl scope,
f:reference to function f
},
scope:[
checkScopeContext.AO,
globalContext.VO
]
}
fContext = {
AO:{
arguments:{
length:0
}
},
scope:[
fContext.AO
checkScopeContext.AO,
globalContext.VO
]
}
执行顺序图和2.3case2图相同
(自由变量:在执行上下文栈时,已经在栈中弹出,但是可以在作用域链中找到的变量)
此时,虽然在上下文栈中,已经弹出了checkscope,但是可以通过作用域链找到,内存并没有释放
变量scope就是自由变量,函数f形成闭包
闭包:当前函数内部变量在外部使用时,内存地址没有释放,形成闭包
闭包是一种表现结果
闭包优点:能够引入自由变量(获取属性方便)
闭包缺点:不知道什么时候释放,可能导致内存泄露(导致全局污染) (解决:垃圾回收机制)
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
使用回调函数就是使用闭包
将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。
内部函数(作为参数或以其他方式)传递到原本词法作用域以外,在执行这个函数时,就形成闭包
你就会看到闭包在这些函数中的应用。在定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!
说明闭包,最好例子for循环
//例1:
for (var i=1; i<=5; i++) {
setTimeout(function timer() {
console.log(i);//5个6
}, i*1000 );
}
//延迟函数的回调会在循环结束时才执行
//所有的回调函数依然是在循环结束后才会被执行
//加入立即执行函数,增加作用域,也不行,如下
//例2:
for (var i=1; i<=5; i++) {
(function() {
setTimeout(function timer() {
console.log(i);//5个6
}, i*1000 );
})();
}
//需要有自己的变量,用来在每个迭代中储存i的值:
//例3:
for (var i=1; i<=5; i++) {
(function() {
var j = i;
setTimeout(function timer() {
console.log(j);//1到5
}, j*1000 );
})();
}
//对这段代码进行一些改进:
//例4:
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j*1000 );
})(i);
}
//总结:以上4个例子中,i都是全局作用域,先执行同步循环任务,
//每次循环i得到一个新的值,循环结束后i的值为6,
//然后执行setTimeout的回调函数timer,此时调用i,得到全局变量i的值为6。
// 例3和例4中,在新的作用域中,新建变量,每次循环,都生成新的作用域,
//并存储i的值,再次调用setTimeout时,取得对应作用域中j的值。
// 所以如果用let,直接生成新的块作用域,可以达到相同的效果
在迭代内使用IIFE会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问
for循环每次迭代时都会重新声明一次并重新赋值
块作用域和闭包联手
for (let i=1; i<=5; i++) {
setTimeout(function timer() {
console.log(i);
}, i*1000 );
}
参考
- 《你不知道的JavaScript》
最后
这是JavaScript系列第4篇,下一篇更新《模块》。
小伙伴如果喜欢我的分享,可以动动您发财的手关注下我,我会持续更新的!!!
欢迎评论讨论交流