乐趣区

关于前端:前端百题斩013用闭包问题征服面试官

写该系列文章的初衷是“让每位前端工程师把握高频知识点,为工作助力”。这是前端百题斩的第 13 斩,心愿敌人们关注公众号“执鸢者”,用常识武装本人的头脑。

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

上图中展现了各个期间的调用栈,须要重点关注以下几点:

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

13.4 优缺点

  1. 长处

    (1)能够重复使用变量,并且不会造成变量净化;

    (2)能够用来定义公有属性和公有办法

  2. 毛病

    (1)会产生不销毁的上下文,导致栈 / 堆内存耗费过大

    (2)会造成内存泄露。

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

  1. 如果闭包引入的函数是一个全局变量,那么闭包会始终存在直到页面敞开;但如果这个闭包当前不再应用的话,就会造成内存泄露;
  2. 如果援用闭包的函数是一个局部变量,等函数销毁后,在下次 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);
}

1. 如果感觉这篇文章还不错,来个分享、点赞吧,让更多的人也看到

2. 关注公众号执鸢者,与号主一起斩杀前端百题。

退出移动版