JavaScript复习—闭包
什么是闭包
闭包是可以访问另一个函数内部变量的函数,闭包的使用则是函数在它定义时的作用域以外被调用。
闭包是怎么形成的
闭包形成的本质其实是,存在指向父级作用域的引用。
let value = 1;
function f1() {
let value = 2;
function f2() {
let value = 3;
console.log(value); // 这里引用了父级f1的作用域
}
return f2;
}
let x = f1();
x(); // 2
在ES5中存在两种作用域——全局作用域和函数作用域。
在上面这个栗子中,x是对f2函数的引用,而f2函数指向window、f1和f2本身的作用域,这就是一条作用域链。当访问value
这个变量时,至底层往上寻找,先在f2本身的作用域中寻找,没找到就顺着作用域链到f1的作用域中寻找,找到了就返回该变量的值,如果找到window作用域了都没找到则报错。
作用域链
用下面的这个栗子来分析理解一下作用域链:
function foo() {
let a = 1;
function bar() {
let a = 2;
function baz() {
let a = 3;
console.log(a);
}
baz();
}
bar();
}
foo();
在全局作用域中调用foo()时,创建了foo的临时OA对象:
foo-OA { a: 1 bar: function() }
在foo函数中调用了bar函数,同样生成了bar的临时OA对象:
bar-OA { a: 2 baz: function() }
在bar函数中调用baz函数,生成baz的临时OA对象:
baz-OA { a: 3 }
这个时候就可以将作用域链连起来啦:window -> foo-OA -> bar-OA -> bar-OA;
于是我们访问变量a的时候,顺着底层开始寻找,在baz-OA对象中找到了a=3的结果;如果我们把baz-OA对象中的a:3
去掉,那么JS引擎会找到bar-OA对象中的a=2,同理往下类推,如果最后在全局中也没有找到,就会报错。
再看看下面的这个栗子:
let a = 1;
function foo(){
let a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
let a = 3;
fn();
}
foo();
看看这个栗子,它输出的结果是2,不是3哦!
让我们看看作用域链:
作用域链:
window {
a: 1
foo: function()
}
-> foo-OA {
a: 2
baz: function()
}
-> bar-OA {
a: 2
baz: function()
}
-> baz-OA {
a: -> foo-OA.a // 访问了foo-OA的a,形成了闭包,把foo的a包裹到了它的作用域里。
}
简单一句话就是:baz函数形成了闭包,把foo的a变量包裹到自己的作用域里,而baz作为参数传给了bar。
看看上面的作用域链,虽然baz没有自己作用域中的a变量,但是在定义的时候访问了foo-OA的a,形成了闭包,把foo的a包裹到了它的作用域里。所以在调用形成作用域链的时候,baz执行console.log的时候,要去找a变量,那么baz-OA中有a,所以输出2,并不用找到链的上层bar-OA中的a。
闭包的表现形式?(那些地方体现了闭包?)
返回一个函数
let value = 1; function f1() { let value = 2; function f2() { let value = 3; console.log(value); } return f2; } let x = f1(); x();
做为函数参数传递
let a = 1; function foo(){ let a = 2; function baz(){ console.log(a); } bar(baz); } function bar(fn){ fn(); } foo();
在定时器,事件监听,Ajax请求或者任何异步操作中,只要使用了回调函数,实际就是使用了闭包!
// 定时器 setTimeout(function timeHandler() { console.log('延时了100毫秒') },100) // 事件监听 $('#app').click(function() { console.log('DOM Listener'); })
IIFE(立即执行函数)创建闭包。
var a = 2; (function IIFE(){ // 输出2 console.log(a); })();
如何销毁闭包
看看这个简单的栗子,这是最简单的返回函数的闭包:
function foo() {
let a = 4;
return function() {
console.log(a);
}
}
let bar = foo();
当执行let bar = foo()
时,形成了foo-OA对象:
foo-OA {
a: 4
}
一般的函数执行完之后,临时OA是会被消除的,但是这里不会
bar -> 引用foo返回函数 -> 返回函数闭包中包裹着foo-OA中的a变量
因此bar一直保持着对foo-OA的引用,所以当foo()执行结束后并不会销毁foo-OA对象。
那么我们怎么使用闭包呢?
bar();
这样就成功调用了,并且形成了bar的临时OA:
bar-OA {
a: -> foo-OA.a
}
在bar调用结束后,bar-OA被销毁了,但是bar对foo-OA.a的引用还存在呀!
那么我们只需要bar = null 就可以销毁引用,进而销毁foo-OA对象了。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 610193653@qq.com,谢谢啦!
文章标题:JavaScript复习—闭包
本文作者:zzzwyyy
发布时间:2019-12-14, 18:50:05
最后更新:2019-12-16, 12:36:44
原始链接:http://yoursite.com/2019/12/14/JavaScript复习—闭包/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。