前端百题斩【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);
}


相关文章
|
28天前
|
Web App开发 前端开发 Linux
「offer来了」浅谈前端面试中开发环境常考知识点
该文章归纳了前端开发环境中常见的面试知识点,特别是围绕Git的使用进行了详细介绍,包括Git的基本概念、常用命令以及在团队协作中的最佳实践,同时还涉及了Chrome调试工具和Linux命令行的基础操作。
「offer来了」浅谈前端面试中开发环境常考知识点
|
9天前
|
存储 前端开发 JavaScript
前端必备知识:闭包的概念、作用与应用
前端必备知识:闭包的概念、作用与应用
12 1
|
2月前
|
存储 XML 移动开发
前端大厂面试真题
前端大厂面试真题
|
5天前
|
Web App开发 JavaScript 前端开发
前端Node.js面试题
前端Node.js面试题
|
2月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
【8月更文挑战第18天】
44 2
|
2月前
|
存储 JavaScript 前端开发
2022年前端js面试题
2022年前端js面试题
35 0
|
2月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
44 个 React 前端面试问题
|
2月前
|
存储 JavaScript 前端开发
|
2月前
|
Web App开发 存储 缓存
|
2月前
|
开发者 图形学 C#
深度解密:Unity游戏开发中的动画艺术——Mecanim状态机如何让游戏角色栩栩如生:从基础设置到高级状态切换的全面指南,助你打造流畅自然的游戏动画体验
【8月更文挑战第31天】Unity动画系统是游戏开发的关键部分,尤其适用于复杂角色动画。本文通过具体案例讲解Mecanim动画状态机的使用方法及原理。我们创建一个游戏角色并设计行走、奔跑和攻击动画,详细介绍动画状态机设置及脚本控制。首先导入动画资源并添加Animator组件,然后创建Animator Controller并设置状态间的转换条件。通过编写C#脚本(如PlayerMovement)控制动画状态切换,实现基于玩家输入的动画过渡。此方法不仅适用于游戏角色,还可用于任何需动态动画响应的对象,增强游戏的真实感与互动性。
74 0