前端百题斩【013】——用“闭包”问题征服面试官

简介: 前端百题斩【013】——用“闭包”问题征服面试官

640.jpg

13.1 定义


在JavaScript中,根据词法作用域的规则,内部函数总是可以访问其外部函数声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,就把这些变量的集合称为闭包。


13.2 闭包实现


在一个函数中嵌套另一个函数或者将一个匿名函数作为值传入另一个函数中。


// 函数fun1中嵌套了fun2,fun2作为参数返回,外部调用时仍能打印val1,构成闭包
function fun1() {
    const val1 = 10;
    function fun2() {
        console.log(val1);
    }
    return fun2;
}
function fun3() {
    const val2 = 20;
    // 定时器中的为一个匿名函数,其作为参数传入了,函数fun3执行完毕之后,1s钟后才会执行定时器函数,但此时还能打印val2,构成闭包
    setTimeout(function() {
        console.log(val2);
    }, 1000);
}

13.3 流程


根据下面的函数来看看闭包的整个执行流程


function main() {
    const val1 = 20;
    var val2 = 2
    function valResult() {
        return val1 * val2;
    }
    return valResult;
}
var result = main();
console.log(result()); // 40


640.png

640.png

640.png


上图中展示了各个时期的调用栈,需要重点关注以下几点:


  1. 当main函数执行完毕后,main函数的执行上下文从栈顶弹出;
  2. 返回的方法(valResult)中调用了main函数中的val1和val2变量,这两个变量就会打包成closure闭包,加到[[scopes]];
  3. 调用返回的方法时,作用域链为:result函数作用域——Closure(main)——全局作用域


13.4 优缺点


  1. 优点


(1)可以重复使用变量,并且不会造成变量污染;
(2)可以用来定义私有属性和私有方法


  1. 缺点


(1)会产生不销毁的上下文,导致栈/堆内存消耗过大
(2)会造成内存泄露。


扩展:闭包是怎么回收的?


  1. 如果闭包引入的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄露;


  1. 如果引用闭包的函数是一个局部变量,等函数销毁后,在下次JavaScript引擎执行垃圾回收时,判断闭包内容已经不再被使用,则js引擎的垃圾回收器就会进行回收。

13.5 用途


闭包用途主要有以下两个:


  1. 创建私有变量


function MyName(name) {
    return {
        getName() {
            return name;
        }
    }
}
const myName = MyName('lili');
// 只能通过getName访问对应的名字,别的方式访问不到
console.log(myName.getName()); // lili
  1. 作为回调函数。当把函数作为值传递到某处,并在某个时刻进行回调的时候就会创建一个闭包。例如定时器、DOM事件监听器、Ajax请求。


function fun(name) {
    setTimeout(() => {
        console.log(name);
    }, 1000);
}
fun('linlin');

13.6 经典闭包问题


多个子函数的[[scope]]都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。


for (var i = 1; i < 5; i++) {
    setTimeout(() => console.log(i), 1000);
}

上述代码本意是输出1、2、3、4,但结果却是四个5,为了解决该问题,主要有三种办法。


  1. 变量可以通过 函数参数的形式 传入,避免使用默认的[[scope]]向上查找


for (var i = 1; i < 5; i++) {
    (function(i) {
        setTimeout(() => console.log(i), 1000);
    })(i);
}
  1. 使用setTimeout包裹,通过第三个参数传入。(注:setTimeout后面可以有多个参数,从第三个参数开始其就作为回掉函数的附加参数)


for (var i = 1; i < 5; i++) {
    setTimeout(value => console.log(value), 1000, i);
}
  1. 使用 块级作用域,让变量成为自己上下文的属性,避免共享


for (let i = 1; i < 5; i++) {
    setTimeout(() => console.log(i), 1000);
}


相关文章
|
20天前
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
54 1
|
2月前
|
Web App开发 前端开发 Linux
「offer来了」浅谈前端面试中开发环境常考知识点
该文章归纳了前端开发环境中常见的面试知识点,特别是围绕Git的使用进行了详细介绍,包括Git的基本概念、常用命令以及在团队协作中的最佳实践,同时还涉及了Chrome调试工具和Linux命令行的基础操作。
「offer来了」浅谈前端面试中开发环境常考知识点
|
1月前
|
存储 前端开发 JavaScript
前端必备知识:闭包的概念、作用与应用
前端必备知识:闭包的概念、作用与应用
17 1
|
3月前
|
存储 XML 移动开发
前端大厂面试真题
前端大厂面试真题
|
1月前
|
Web App开发 JavaScript 前端开发
前端Node.js面试题
前端Node.js面试题
|
3月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
【8月更文挑战第18天】
51 2
|
3月前
|
存储 JavaScript 前端开发
2022年前端js面试题
2022年前端js面试题
38 0
|
3月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
44 个 React 前端面试问题
|
3月前
|
存储 JavaScript 前端开发
|
3月前
|
Web App开发 存储 缓存