知食记
搜索文档…
知食记
思维导图
归档
博客
🎃CSS
CSS基础
CSS3
SCSS
🎉JavaScript
JS 概念
类型
类型转换
内存
原型链
继承
词法作用域
事件委托
Falsy
This
闭包
Event-loop
跨域
function.length
arguments
!!
void 0
柯里化1
柯里化2
异常
协程
JS陷阱
JS开发知识点
实现JS常见函数
实现JS 常见操作函数
JS Worker
ES6
ES6 函数
Typescript
V8
🕹️框架
Vue
Vue3
React
React-Redux
React Hooks
Nuxt
Koa2
🎯算法
算法与数据结构
🎁HTML
DOM
SVG
🏈计算机网络
浏览器
计算机网络
🥊前端生态
Webpack
Babel
Fetch
Axios
Npm
Yarn
业务开发
微前端
Hexo
🏀后端
Node
Java
Python
🕹️面试
面试真经
To-do
🤖开源
开源项目
🧸其他
Linux
Git
正则
设计模式
计算机理论
Group 1
由
GitBook
提供支持
闭包
定义
闭包是指那些能够访问自由变量的函数。
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
1
var
a
=
1
;
2
3
function
foo
()
{
4
console
.
log
(
a
);
5
}
6
7
foo
();
Copied!
上例函数 foo + foo 函数访问的自由变量 a也算是闭包。所以在《JavaScript权威指南》中就讲到:
从技术的角度讲,所有的JavaScript函数都是闭包
。
但是从实践上意义的闭包定义为:
1.
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
2.
在代码中引用了自由变量
闭包的例子
1
function
foo
()
{
2
var
a
=
2
;
3
function
bar
()
{
4
console
.
log
(
a
);
5
}
6
7
return
bar
;
8
}
9
10
var
baz
=
foo
();
11
baz
();
// 2 —— 朋友,这就是闭包的效果。
Copied!
在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃 圾回收器用来释放不再使用的内存空间。由于看上去 foo() 的内容不会再被使用,所以很 自然地会考虑对其进行 回收 。
而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域 没有被回收。谁在使用这个内部作用域?原来是 bar() 本身在使用。
拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一 直存活,以供 bar() 在之后任何时间进行引用。
bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。
陷阱1
1
for
(
var
i
=
1
;
i
<=
5
;
i
++
)
{
2
setTimeout
(
function
timer
()
{
3
console
.
log
(
i
);
4
},
i
*
1000
);
5
}
Copied!
上诉代码,最后只会连续的输出6,而不是1到5。
这背后的原因是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的, 但是
它们都 被封闭在一个共享的全局作用域中 ,因此实际上只有一个 i
。
这样说的话, 当然所有函数共享一个 i 的引用。循环结构让我们误以为背后还有更复杂的机制在起作用,但实际上没有。
如果将延迟函数的回调重复定义五次,完全不使用循环, 那它同这段代码是完全等价的。
个人更多的理解是,参考event-loop机制,setTimeout是宏任务,不会被同步执行,会先注册。又由于在i*1000所有同步执行完后,真正执行timer时,i值已经被改变了
解决问题的方式是,更改成立即执行函数
1
for
(
var
i
=
1
;
i
<=
5
;
i
++
){
2
(
function
(
j
){
3
setTimeout
(
function
timer
()
{
4
console
.
log
(
j
);
5
},
j
*
1000
);
6
})(
i
)
7
}
Copied!
当然,更酷的是es6的写法:
1
for
(
let
i
=
1
;
i
<=
5
;
i
++
)
{
2
setTimeout
(
function
timer
()
{
3
console
.
log
(
i
);
4
},
i
*
1000
);
5
}
Copied!
陷阱2
1
function
Foo
()
{
2
var
i
=
0
;
3
return
function
()
{
4
console
.
log
(
i
++
);
5
}
6
}
7
8
var
f1
=
Foo
(),
9
f2
=
Foo
();
10
f1
();
11
f1
();
12
f2
();
Copied!
道题考察
闭包
和
引用类型对象
的知识点: 1.一般来说函数执行完后它的局部变量就会随着函数调用结束被销毁,但是此题foo函数返回了一个匿名函数的引用(即一个
闭包
),它可以访问到foo()被调用产生的环境,而局部变量i一直处在这个环境中,只要一个环境有可能被访问到,它就不会被销毁,所以说闭包有延续变量作用域的功能。这就好理解为什么:
1
f1
();
//0
2
f1
();
//1
Copied!
2.我一开始认为f1和f2都=foo()是都指向了同一个function引用类型,所以顺理成章就会答错认为:
1
f2
();
//2
Copied!
但其实foo()返回的是一个匿名函数,所以f1,f2相当于指向了两个不同的函数对象,所以结果也就顺理成章的变为:
1
f2
();
//0
Copied!
注意
如果上面的代码改成下面,结果会改变
1
const
Foo
=
(
function
()
{
2
var
i
=
0
;
3
return
function
()
{
4
console
.
log
(
i
++
);
5
}
6
})()
7
8
var
f1
=
Foo
,
9
f2
=
Foo
;
10
f1
();
// 0
11
f1
();
// 1
12
f2
();
// 2
Copied!
其本质区别为, Foo变成立即运行函数,已经生成了确定的匿名函数地址,再分别分配给 f1和 f2
闭包的应用场景
vue 源码中 全局Api中的$watch方法中使用闭包
1
// 根据Vue的$watch原理实现的简易版$watch
2
Vue
.
prototype
.
$watch
=
function
(
exp
,
cb
,
options
=
{
immediate
:
true
,
deep
:
false
}
)
{
3
let
watcher
=
new
Watcher
(
this
,
exp
,
cb
,
options
);
4
return
()
=>
{
5
watcher
.
unWatch
();
6
};
7
}
Copied!
应用
1
let
obj
=
{
2
name
:
'kiner'
,
3
age
:
20
4
};
5
6
// 监听obj.name的改变
7
let
unWatchName
=
this
.
$watch
(
"obj.name"
,
function
(
newVal
,
oldVal
){
8
console
.
log
(
`
新值:
${
newVal
}
;旧值:
${
oldVal
}
;
`
);
9
});
10
11
// 取消监听,取消后,obj.name的改变不再会通知
12
unWatchName
()
Copied!
这样实现的巧妙之处在于,我们无须关心要调用谁去取消监听,你怎么监听的,他就给你返回一个取消监听的方法,直接调用这个方法取消就可以了。
闭包应用
节流
防抖
缓存
1
const
Id2NameMap
=
{};
2
const
getNameById
=
function
(
id
)
{
3
4
if
(
Id2NameMap
[
id
])
return
Id2NameMap
[
id
];
5
6
return
new
Promise
(
resolve
=>
{
7
mockGetNameById
(
id
,
function
(
name
)
{
8
Id2NameMap
[
id
]
=
name
;
9
resolve
(
name
);
10
})
11
});
12
}
13
Promise
.
resolve
(
getNameById
(
id
)).
then
(
name
=>
{
14
console
.
log
(
name
);
15
});
Copied!
Once
1
/**
2
* Ensure a function is called only once.
3
*/
4
export
function
once
(
fn
:
Function
)
:
Function
{
5
let
called
=
false
6
return
function
()
{
7
if
(
!
called
)
{
8
called
=
true
9
fn
.
apply
(
this
,
arguments
)
10
}
11
}
12
}
Copied!
以前
This
下一个
Event-loop
最近更新
2yr ago
复制链接
内容
定义
闭包的例子
陷阱1
陷阱2
注意
闭包的应用场景
闭包应用
节流
防抖
缓存
Once