js闭包
先看一段代码例子
function outer() {
let count = 0;
return function () {
count++;
console.log(count);
};
}
const fn = outer();
fn(); // 1
fn(); // 2
fn(); // 3
在上面一段代码中,很多人或许已经已经习以为常。但其实仔细想象,在outer函数执行完毕,内部的变量其实随后应该要被GC回收才对;而且在外部执行fn函数,为什么还可以访问到count呢?let不是块级作用域吗
其实这样的现象,就是我们常说的闭包。
闭包是:闭包是函数和它的词法环境的组合。这个是一个比较抽象的说法。形象一点,也可以说:一个函数记住了它外部函数的变量,即使他在其他地方调用。
高度抽象的说法其实更能反应原理的。在外部执行fn函数,其实就是执行outer函数,那么从fn开始找count, 没有找到,是undefined或者报错。像这样一个经典例子:
let a = 0
function fn () {
console.log(a)
}
function fn2 () {
let a = 1
fn()
}
fn2() // 0
如果你用”面向过程“一样的想法,首先执行fn2函数,然后定义a = 1,执行fn函数,然后fn函数执行的地方往外找,打印1。 但其实是打印0。
JavaScript 采用的是词法作用域。一个函数能访问哪些变量,不取决于它在哪里调用,而取决于它在哪里定义。
回到最上面闭包的例子,它在outer函数内定义的,自然可以通过作用域链找到count,即使他是在外面执行的。
为什么变量没有被销毁
解释了为什么可以访问,但是”记住“是怎么做到的呢。是因为返回出去的内部函数,仍然引用着外层作用域中的变量。只要这个函数还存在,这些变量对应的作用域环境就是“可达的”,GC 就不能回收它。
或者可以粗略的认为,count这个变量,被标记一处引用,计数不为0,不能被GC回收。细想返回的函数由fn接收,随时可以被调用,如果GC回收了count,不就报错了吗。
闭包的用处
- 保存状态,这个在开头代码已经做了很好的解释,保存了count的状态
- 私有变量,想想一个私有的变量,我不想别人直接获取或者更改,只能通过我提供的方法操作,闭包是个很好的方式。这也是早期模块化的做法
const counter = (function () {
let count = 0
return {
inc() {
count ++
}
get() {
return count
}
}
})()
- 函数工厂,这一个其实有很多的应用,比如经典的节流、防抖
const debouncedSearch = debounce(search, 300)
又没想过你只传了一个search函数,一个间隔时间。你的新函数怎么记住你传入的300毫秒,在你每次输入时重新计算的?让我们写一个最简单的防抖
function debounce(fn, wait) {
let timer = null
return function(...args) {
if (timer) clearTimout(timer)
setTimeOut(() => {
fn.apply(this, args)
}, wait)
}
}
之所以可以到重新计算,是因为 function 新函数,记住了timer,每次执行时都重新等待。等待的时间,则由setTimeout这个函数的回调函数记住wait。
节流是类似的原理,就不赘述
评论 (0)
为了减少垃圾评论,请先使用 GitHub 登录后再发表评论。
使用 GitHub 登录评论暂无评论,成为第一个评论者!