面试常考:聊一聊闭包
前言
闭包(Closure)是 JavaScript 中一个非常重要的概念,它允许一个函数访问并操作其外部作用域中的变量,即使在该函数被外部作用域之外的地方调用时也是如此。
闭包是JS面试的一大考点,也是一大难点,今天就来聊一聊闭包,将闭包的知识点吃透。
正文
作用域链
看到下面这段代码,你知道打印结果是什么吗?知道打印结果是Jerry
,那你知道原因吗?
function bar() {
console.log(myName); // 打印结果为 Jerry
}
function foo () {
var myName = 'Tom'
bar()
}
var myName = 'Jerry'
foo()
在作用域链中,outer
指向的是外部(上一级)执行上下文的作用域,像 bar 函数声明在了全局,那bar 的 outer 就指向全局。如下图所示,bar 函数 的执行会先在 bar 的执行上下文里面找 myName,找不到就会顺着作用域链查找,就在全局作用域中找到了 myName 的值为 Jerry。
作用域链并不是在调用栈中从上到下查找,而是看当前执行上下文变量环境中的outer指向来定,而outer指向的规则是,我的词法作用域在哪里,outer就指向哪里。
词法作用域
词法作用域由函数声明的位置来决定,跟函数在哪里调用没有关系,如下图所示。
块级作用域
let和{}形成块级作用域
如下面这段代码展示,打印结果是 10 个 10,和我们预期的结果为 0-9 不一样,这是因为 i 是在全局声明的,执行结果时 i 的值已经为 10 了。
var arr = []
for (var i = 0; i < 10; i++) {
arr[i] = function() {
console.log(i);
}
}
for (var j = 0; j < arr.length; j++) {
arr[j]()
}
如果将 var i
改为 let i
,那么得到的结果就是 0-9 ,这是因为 let 和 {} 形成块级作用域,每循环一次就会形成一个块级作用域,最后 outer 会指向块级作用域,因此打印结果为 0-9。
闭包
在javaScript中,根据词法作用域的规则,内部函数一定能访问外部函数中的变量,当内部函数被拿到外部函数之外调用时,即使外部函数执行完毕,但是内部函数对外部函数中的变量依然存在引用,那么这些被引用的变量会以一个集合的方式保存下来,这个集合就是闭包。
在下面这个例子中,内部函数是 bar
,外部函数是 foo
,bar函数在foo函数外面被调用,foo函数执行完毕,但是bar引用了foo中的 myName
,那么这个被引用的变量 myName 就被保存下来形成了闭包。
function foo() {
function bar() {
var age = 18
console.log(myName);// 打印结果为 'Tom'
}
var myName = 'Tom'
return bar
}
var myName = 'Jerry'
var fn = foo()
fn()
上面这段代码会先预编译,先形成一个全局执行上下文,接着调用foo函数,形成了foo执行上下文,foo返回出来了bar,已经完成了foo的使命了,这时候就会回收foo,但是又因为bar被调用了,还用到了foo中的变量,这时候就会回收除了 myName
的其他东西,而 myName
就被存放在了闭包
里面。
如上图所示,最后bar的outer会指向foo,打印结果为
Tom
。
闭包的运用
闭包的作用:实现变量私有化
用代码实现一个累加器,这时候可以运用到闭包,如下所示,函数add返回了fn,add执行上下文留下了闭包,闭包中的变量count会保留到下一次调用。
function add() {
let count = 0
function fn() {
count++
return count
}
return fn
}
var res = add()
console.log(res()); // 1
console.log(res()); // 2
console.log(res()); // 3
结语
在实际开发过程中,合理地使用闭包能够提升代码的质量,但也需要注意避免不当使用导致内存泄漏等问题。希望本文能帮助你更好地理解和应用闭包这一概念。
转载自:https://juejin.cn/post/7397019920322052123