2019独角兽企业重金招聘Python工程师标准>>>

前言

es6之前,js的作用域只有两种,全局作用域和函数作用域,没有像C和java那样的块级作用域,于是对于学了C或者java这类语言的然后学习js的同学来说,会遇到很多坑。js的这个特性导致了代码的可阅读性、维护性和容错性都不太好。因此es6可以用let来申明变量,这种方式申明的变量是只能在块作用域里访问,不能跨块访问,也不能跨函数访问。那么我们在使用let的时候,真的就完全知道它怎么用了吗?

引子

看到这样的一个面试题

for(leti = (setTimeout(()=>console.log(i), 2333) , 0); i < 2; i++) {  }复制代码

大家猜猜2333毫秒后输出的结果是什么?这里就是“血案”现场了

A类同学:2 ×

B类同学: 0 √

我想A类同学占了大多数,包括我在内

前期知识点

异步

js中的异步包含以下几种:

1、定时器

2、事件处理函数

3、Promise

4、回调函数

js异步的存在是因为,js是单线程的,如果一些任务需要处理时间比较耗时,那么下面的任务就会一直等这个任务执行完成才能继续,比如一些IO任务,这样就会导致执行效率低效,所以js的设计者意识到了这点,设计了异步执行任务,主线程不必等待异步任务完成才执行下去,这样我们就可以把一些耗时的任务设计成异步任务,将其挂起,让主线程处理完一些比较重要的任务(ui渲染等)后回头再来执行挂起的异步任务。

作用域链

js存在两种类型的作用域,全局作用域和函数作用域。js执行的时候,会创建一个执行上下文(context),并将该执行上下文中的所有变量放入一个对象中[[scope]],该对象也叫作用域链,它不仅保存了函数执行时的上下文的所有变量(活动对象),还包含了创建该函数的作用域中的所有变量。通俗点来讲,作用域链就是js在执行的时候用于搜索变量所在的一条链子,所有变量的获取变量会顺着这条链子往上查找,在本作用域内找不到变量的申明,就会往上一级的作用域中查找,直到在全局作用域中还找不到,就找不到该变量了。看下面的例子。

var outer = 1;functionfunc1() {    var inner1 = 2;functionfunc2() {        var inner2 = 3        console.log(inner2, inner1, outer); // 3 2 1    }    func2()}func1();复制代码>need-to-insert-img

1、首先获取inner2,在func2的作用域中(活动对象)找到了inner2的申明,找到了,并且是3;

2、接着获取inner1,发现func2的作用域中没有inner1的申明,那么往创建func2的作用域中查找,即func1中查找inner1的申明,并且为2;

3、接着获取outer,在func2中的作用域中找不到,往作用域链的上一级找,func1中也没有outer的申明,那么就继续往上一级找,在全局作用域中找到了outer,所以是1。

接着我们讲下闭包,所谓闭包用一句话来说就是,函数中的函数,并且里面的函数引用了外面的函数的变量。我们了解了作用域链,那么我们就知道,函数内部是可以访问函数外部的变量的,所以,如果我们在函数中的函数中有访问函数外部变量,且该内部函数被返回的时候就形成了闭包。看下面例子:

functionfunc() {    var name ='liming';    var sayName =function() {        console.log(name)    }returnsayName;}var sayName = func();sayName(); // 输出liming复制代码

如上面,就是闭包的一个例子,总结开来有两个特点:

1、外部函数包含内部函数,且内部函数访问的外部函数的变量

2、返回内部函数给外部调用

闭包有个缺陷就是容易导致内存泄漏,普通函数调用完后,js引擎就会销毁函数里面的变量,但是闭包的话就不会释放了,所以需要注意点。

解析

选答案A的同学

对于A类同学,答案是错的,但是可以看出A类同学对js的异步和闭包比较熟悉。我们知道setTimeout里面的函数是异步执行的,属于js里面的宏任务(js的异步任务分宏任务和微任务),需要等待js的主线程执行完毕且等到设置的时间后才从宏队列里面取出来执行。所以,等到setTimeout的回调执行的时候,回调函数要获取i的值,这个时候回到函数里面没有i的定义,那么js引擎就会往上一级作用域链中找i,这个时候就找到上一级作用域中的i,A类同学觉得这个时候循环已经结束了(因为for是主线程),那么这个时候的i应该是2了,所以输出的应该++了两次的2。这也就是闭包的知识点,js的设计是,内部可以访问外部,而外部不可以访问内部,所以在setTimeout中的回调中,它可以访问得到外部的i,其实如果把let换成var的话,这个答案就是对的。

关于倒计时,这里有个东西多说一句,就是setInterval的倒计时不是在回调执行完毕后才开始的。这就会导致一种情况,就是如果回调函数里面执行的代码时间比倒计时时间长,那么下次插入队列中的回调就会被取消,也就是倒计时到了以后,这次回调不会执行了,所以建议统一使用setTimeout来代替setInterval。

选择B的同学

选择B的同学,要不就是刚学习js的(也可能是蒙对☺),要不就是对let知识点很熟悉的。

let关键字

let关键字申明的变量具有块级作用域的作用,具有以下特点:

1、不可重复申明同个变量

2、不存在变量提升,所以必须先申明后使用

3、只有块内可见,不会影响块外的变量

其实let还有一个特点,就是在for循环当中,每轮循环都是一个新的值。看下面的的例子:

for(leti = 0; i < 2; i++) {setTimeout(() => {        console.log(i); // 分别输出0和1    }, 0)}复制代码

从这个例子可以看出,let变量在for循环中,都会被重新赋值一个新的值,因此上面代码中,for循环中获取的i值都是一个新的,并且这个新i的值是上一次循环的i的值。类似这样的伪代码:

for(var i = 0; i < 2; i++) {    var new_i = i; // 新的i,且新的i应该是和真正的i关联的,比如是new_i_0、new_i_1之类的,这段是伪代码,用来说明,评论的同学说,let的i是被挟持了,这个解释很赞,所以for中的i其实都是被js引擎挟持了的i,不是我们看到的isetTimeout(() => {        console.log(new_i); // 分别输出0和1    }, 0)}复制代码

个人觉得,这个是let的块级作用域相关,每次循环的时候的i都是块级作用域,只对本次循环可见,下次循环不可见。

所以,我们以后如果需要再for循环中获取循环项的时候,可以不用立即执行函数来实现了,可以改为let了。

回到正题。for循环的第一个语句是初始化,这个时候的i就是原本的i,初始化为0,后面的i都是每次循环新生成的i,与初始化的i无关,所以到2333毫秒以后,i的值任然为0,因此打印出来的i就是0了。

for(leti = (setTimeout(()=>console.log(i), 2333) , 0); i < 2; i++) {  }复制代码

总结

本篇文章通过一个特殊的面试题,引出了js的异步、作用域链、闭包和let的知识点。

异步包含:

1、定时器

2、事件处理函数

3、Promise

4、回调函数

异步函数的执行时需要主线程空闲的时候执行的,所以我们会把耗时的任务处理为异步。

作用域链:

每个函数执行的时候都会创建一个作用域链的对象,它包含了函数内的所有变量以及创建该函数的函数的所有变量,一直到全局变量,访问变量的时候就会沿着这条链子找。

闭包:

1、外部函数包含内部函数,且内部函数访问的外部函数的变量

2、返回内部函数给外部调用

let:

1、不可重复申明同个变量

2、不存在变量提升,所以必须先申明后使用

3、只有块内可见,不会影响块外的变量

还有在for循环中,每次循环获取let声明的变量都是一个新的变量,而不是初始化时候的那个变量。

在这里顺便给大家推荐一个架构交流群:617434785,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。

转载于:https://my.oschina.net/u/3871554/blog/2209664

一道面试题引发的“血案”相关推荐

  1. java yang模型_一道面试题引发的对Java内存模型的一点疑问

    一道面试题引发的对Java内存模型的一点疑问 问题描述如上图所示程序,按道理,子线程会通过 num++ 操作破坏 while 循环的条件,从而终止循环,执行最后的输出操作.但在我的多次运行中,偶尔会出 ...

  2. 5年前面试题引发的“血案”(番外篇)(总结和乱侃)

    这货是说好的番外篇-- 所谓的番外篇其实就是对前面的各个知识点做一些总结. 血案(1)中的两个内容主要是日志切换时的检查点和表空间管理. 日志切换其实远远没有简单,有关于检查点和日志文件的内容太多了, ...

  3. 一道面试题引发的关于程序设计的想法

    申明:这是在看到园子里两个帖子关于两道面试编程题之后个人的一点想法 面试题一: 大厅里有100盏灯,每盏灯都编了号码,分别为1-100.每盏灯由一个开关来控制.(开关按一下,灯亮,再按一下灯灭.开关的 ...

  4. 一道笔试题引发的Promise笔记

    前言 近来参加校招笔试,发现有好几道关于Promise的题目.然而我都没有了解过.所以,这篇文章以网易笔试的一道题开始,记录关于Promise的那些事. 文章地址:http://lsxj615.com ...

  5. 一到关于js函数的前端面试题引发的血案

    题目如下: 1 f = function() { return true; }; 2 g = function() { return false; }; 3 (function() { 4 if (g ...

  6. 一道面试题引发的对JavaScript类型转换的思考

    最近群里有人发了下面这题: 实现一个函数,运算结果可以满足如下预期结果: add(1)(2) // 3 add(1, 2, 3)(10) // 16 add(1)(2)(3)(4)(5) // 15 ...

  7. 一道HashSet面试题引发的蝴蝶效应

    没错,我又借着"面试题"的名头来搞事情了,今天要说的是 HashSet ,而这确实是一个实际面试中遇到的问题.当时的场景大概是这样的,面试官在了解了你的知识广度以后,决心来考察一番 ...

  8. 「一道面试题」输入URL到渲染全面梳理中-页面渲染篇

    前置知识 此文是一道面试题,又不仅仅是一道面试题,不过这道题共分了三篇来说,嗯..可想而知 接上文,上文我们讲了网络通信的部分,详细请看「一道面试题」输入URL到渲染全面梳理上-网络通信篇, 那么该说 ...

  9. 一个随机数引发的血案

    一个随机数引发的血案 我也来做一次标题党 原文地址:http://blog.csdn.net/WinsenJiansbomber/article/details/50604653 目录 一个随机数引发 ...

最新文章

  1. 一个超干货的3D视觉学习社区
  2. 看板中的WIP限制思想 1
  3. 【深度学习】制作VOC2007数据集用于Faster-RCNN训练
  4. npm script 的实践
  5. Amcharts 柱状图和线形图
  6. jQuery_基本选择器
  7. 什么是mysql的主从复制?
  8. .NET 指南:安全编码概览
  9. python open写入_Python IO操作文件读取和写入、open函数的mode参数、buffering,文件缓冲区...
  10. 汇编实现时钟设置代码理解
  11. input位置_3分钟短文 | PHP 数组任意位置插入新元素,你是怎么处理的?
  12. 1688: [Usaco2005 Open]Disease Manangement 疾病管理( 枚举 )
  13. 汇编64位无法生成可用exe_MASM学习x86汇编语言2 寄存器、伪指令与程序调试
  14. 多媒体计算机主机有哪些硬件,现在市场上多媒体电脑主要有哪些硬件部分组成?...
  15. swagger常用注解汇总
  16. 对话蔡报永:看Commvault 如何玩转数据管理
  17. Basler相机环境配置
  18. 基于优化的多核局部费舍尔判别分析的故障分类
  19. 直播新红海,狼人杀火爆背后的实时语音视频技术 | 深度
  20. 微信会员卡开发之微信公众平台的基本配置

热门文章

  1. 豪情-2014年年终总结
  2. OGRE源代码resource分析
  3. (转)OO设计初次见面
  4. JDK8时间与java.util.Date,java.sql.Date,Timestamp等的相互转换(时间转换)
  5. Servlet 客户端 HTTP 请求
  6. matlab根据给定3点画圆弧_圆弧齿廓面齿轮齿顶尖化研究
  7. c++ map用法_5分钟掌握Python | Map、Reduce和Filter如何运用?
  8. pythonturtle是标准库_Python标准库: turtle--海龟绘图。
  9. raster | R语言中的空间栅格对象及其基本处理方法(Ⅱ):投影、属性提取
  10. 想成为一个Web前端开发工程师,需要掌握的详细知识总结