资讯专栏INFORMATION COLUMN

前端计划——一道经典的JavaScript问题(含闭包、事件轮询、作用域等概念解释)

EdwardUp / 2446人阅读

摘要:作用域和作用域链关于作用域这里不做过多解释,中根据作用域可分为全局变量和局部变量。好吧,不是这部分核心,核心是解释的单线程和事件轮询机制。这部分就涉及到闭包的理解了。

前言:这是一道很经典的Js面试题,涉及到闭包、变量作用域、setTimeout等知识,对于深入理解这些内容很有帮助

题目描述
//问题描述:请写出最终的输出值,并解释原因

var value1 = 0, value2 = 0, value3 = 0;
for ( var i = 1; i <= 3; i++) {
    var i2 = i;
    (function() {
        var i3 = i;
        setTimeout(function() {
            value1 += i;
            value2 += i2;
            value3 += i3;
        }, 1);
    })();
}
setTimeout(function() {
    console.log(value1, value2, value3);
}, 100);
//输出结果:value1=12; value2=9; value3=6
题目解释

首先,为了下面解释这道题,我们先来补充一些预备知识(大神级人物可跳过这部分)

一、基础知识补充与温习 1、闭包和作用域

官方解释:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

function a(){
    var i=0;
    function b(){
        alert(++i);
    }
    return b;
}
var c=a();
c();

方言版:当函数a的内部函数b被外部变量c引用时,就形成了闭包

闭包的作用:我们知道,js里定义在函数内部的是局部变量,外部是无法直接访问的,而它的内部函数可以访问,那么内部函数返回一个值,就相当于类似在外部也能访问局部变量。

闭包的特点:为了使b中能够访问i的值,i不会被内存回收,就实现了内存常驻。对于理解这道题很重要。

闭包的缺点:内部闭包函数可以访问外部函数的变量,所以外部函数的变量不能被释放,如果闭包嵌套过多,会导致内存占用大,出现内存溢出。

作用域和作用域链:关于作用域这里不做过多解释,js中根据作用域可分为全局变量和局部变量。而对于作用域链的简单理解,可以认为当一个函数创建之后,从它的执行环境(当前对象)一直到全局对象建立了一个链表,可用的变量都挂载在上面。而函数需要查找某个变量值时,变回按照从当前直到全局对象来进行查找。

2、事件轮询与setTimeout

简介:setTimeout是js中常见的一个函数,属于window下的方法(通常,大家会省略window)
语法:setTimeout(code,millisec) 参数一为代码,参数二为毫秒数
作用:设定一个时间, 时间到了之后, 就会执行一个指定的函数或表达式,且只执行一次。

好吧,setTimeout不是这部分核心,核心是解释js的单线程和事件轮询机制。
我们知道,js是单线程的,也就是说所有的任务要排队执行。
在js中有同步和异步执行——
同步执行:是指前一个任务执行完,然后下一个任务继续执行,都在主线程里。
异步执行:则是把事情放进“任务队列”(或叫事件队里),而不是在主线程中,它们通过事件轮询(Event Loop)和回调来实现调入主线程执行。
继续回到setTimeout,语法里面的code就是异步执行的部分。
关于setTimeout更详细的内容,可点击这里学习setTimeout那些事
关于事件轮询的学习,请点击这里理解事件轮询

二、对习题的解答

上面都是些基础知识,接下来进入正题。
首先,我们拿到题目,要注意到第一个setTimeout里面匿名函数,这部分其实是放在for循环之后才会执行的,因为它是一个异步执行的函数,被放到了事件队列里最后执行。而且,每次setTimeout里面的函数执行时可以近似理解为是一次实例化。

value1
在计算value1时,需要用到i,这里涉及到作用域链的知识,最内层的函数没有i的值,它会沿着链式结构一直向上查找,最终发现i是for循环执行之后的值。此时,i的循环完成,最后一次i++之后,i已经变成了4。这样,setTimeout执行3次实例化,每次i的值是不变的,最终值为value1=4+4+4=12。

value2

类似于value1,在执行setTimeout里的函数时,需要找到i2的值,最终我们找到的是for循环到第三次时i2=i=3。(i2不会等于4,因为到最后一次i++之后,已经不会再进入循环体了)。所以类似上面,value2的值是3+3+3=9。

value3
这部分就涉及到闭包的理解了。在循环过程中,通过立即执行函数创建了闭包,每次i3都会被赋予当次循环时i的值并保存,i3的值依次为1,2,3,最终value3=1+2+3=6。

以上为个人解释,如有错误,还望各位指正。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86759.html

相关文章

  • JavaScript系列(四) - 收藏集 - 掘金

    摘要:函数式编程前端掘金引言面向对象编程一直以来都是中的主导范式。函数式编程是一种强调减少对程序外部状态产生改变的方式。 JavaScript 函数式编程 - 前端 - 掘金引言 面向对象编程一直以来都是JavaScript中的主导范式。JavaScript作为一门多范式编程语言,然而,近几年,函数式编程越来越多得受到开发者的青睐。函数式编程是一种强调减少对程序外部状态产生改变的方式。因此,...

    cfanr 评论0 收藏0
  • 还担心面试官问闭包

    摘要:一言以蔽之,闭包,你就得掌握。当函数记住并访问所在的词法作用域,闭包就产生了。所以闭包才会得以实现。从技术上讲,这就是闭包。执行后,他的内部作用域并不会消失,函数依然保持有作用域的闭包。 网上总结闭包的文章已经烂大街了,不敢说笔者这篇文章多么多么xxx,只是个人理解总结。各位看官瞅瞅就好,大神还希望多多指正。此篇文章总结与《JavaScript忍者秘籍》 《你不知道的JavaScri...

    tinyq 评论0 收藏0
  • JavaScript系列——JavaScript同步、异步、回调执行顺序之经典闭包setTimeou

    摘要:同步异步回调傻傻分不清楚。分割线上面主要讲了同步和回调执行顺序的问题,接着我就举一个包含同步异步回调的例子。同步优先回调内部有个,第二个是一个回调回调垫底。异步也,轮到回调的孩子们回调,出来执行了。 同步、异步、回调?傻傻分不清楚。 大家注意了,教大家一道口诀: 同步优先、异步靠边、回调垫底(读起来不顺) 用公式表达就是: 同步 => 异步 => 回调 这口诀有什么用呢?用来对付面试的...

    lewif 评论0 收藏0
  • JavaScript系列——JavaScript同步、异步、回调执行顺序之经典闭包setTimeou

    摘要:同步异步回调傻傻分不清楚。分割线上面主要讲了同步和回调执行顺序的问题,接着我就举一个包含同步异步回调的例子。同步优先回调内部有个,第二个是一个回调回调垫底。异步也,轮到回调的孩子们回调,出来执行了。 同步、异步、回调?傻傻分不清楚。 大家注意了,教大家一道口诀: 同步优先、异步靠边、回调垫底(读起来不顺) 用公式表达就是: 同步 => 异步 => 回调 这口诀有什么用呢?用来对付面试的...

    rockswang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<