1.问题

首先把问题放出来,昨天看了一个掘友发的一个问题,然后跟我同事一起研究了一下,没找出来是为什么,然后我回来一直在想为什么,然后各种找资料研究,从各个方面找为什么,比如js上下文,作用域,js垃圾回收,堆栈调用情况等等。

2.js断点调试找答案

首先如果不看上面的图,以你现在知道的js知识,你觉得打印出来应该是什么。第二张图其实打印出来的结果在意料之中,原因就是函数声明提升,没问题,但是第一张图为什么呢?这里可以发散一下思维,比如说是不是在块作用域中,变量和函数之间存在某种互相覆盖的问题啊,或者说先在块中声明的会被挂载到全局的window对象下面,后面声明的就挂载不上去了,并且不会覆盖,然后可以把代码稍微改改,验证一下你的思想,很有意思。然后下面我们断点调试看下:

首先进花括号一步都没走的时候,但是a和b已经挂载到全局变量的window对象下面了,这就说明代码块中隐式声明的变量是全局变量(这句话不对,此时挂载到window对象下面的,其实是函数挂的,并不是隐式变量是全局变量的原因挂上去的参考文章这篇文章,基本是这个问题的答案了,解释了为什么,链接:https://juejin.im/post/5d90ae9ef265da5b646480a0),代码相当于这样:

此时我们再看a在块作用域中就已经是方法了,注意此时我function a(){}这段代码还没走完呢,这就说明函数声明在js解析(注意是解析不是执行)的时候被提升到了代码块顶部,进花括号的那一刻起,函数就已经被声明了,我们再往下面一步走

此时a不管在块作用域还是全局作用域中都变成了a函数,那这里是不是可以理解为运行上面一行代码,然后就给全局变量下的a赋值为函数呢,我们再看下一步

a=50;走完之后,也就出了块作用域,此时我们看到没有了块作用域,因为已经出了块作用域了,然后全局对象window里面a还是函数,并不是50,但是如果你在块作用域a后面加一行的断点看的话,此时块作用域里面的a的值为50,问题就在这里,为什么此时块作用域里面的a的值跟全局window对象下面的值结果不一样呢?

然后我们再往下走一步:

然后进第二个块作用域,发现跟前面进第一个块作用域一样,还没执行第一行,块作用域里面的b已经是函数了,原因也跟第一个一样js解析的时候函数声明提升,然后我们再往下走一步:

这一步走完我们发现块作用域里面的b已经变成50了,但是全局window对象下面的b还是undefined,这我也不知道为什么,那我也就只能说此时b是定义在块作用域中的内部变量了,再往下走一步

但是当我走出块作用域的时候,b竟然在全局对象下变成了50,那就证明我上面说的不对,b不是块作用域中的内部变量,因为此时执行完方法立马就出块作用域了,我们看的不是很清楚,我们在方法下面加一行代码,方便调试看结果:

确实是当我b函数那一步走完,块作用域和全局对象window下面的b都变成了50,那我这里我就认为是函数b在js解析的时候就被提升到了块作用域的最上面,执行到b函数那一步其实在之前就已经执行过了,相当于js执行的时候代码变成下面这样:

{
function b() {};
b = 50;
}

我们再看这个代码不正是上面a那一个块作用域的代码吗,所以在块作用域中js执行的时候上下两个块作用域中是一样的,所以在块作用域中打印a,b得到的结果都是50,然后下一步:

出了块作用域,就只有全局对象window了,然后window对象下面的b还是50,所以最后打印出来也是50。走到这一步就所有的步骤都走完了,那么我们再回头看上面的a为什么块作用域中的值跟window对象下面的a的值不一样,通过走完下面一个代码块我们发现上面代码块跟下面代码块只有函数放的位置不一样,结果就不一样,那我们就看一下这里函数声明提升到底是怎么提升的。

3.块作用域中的函数声明提升

然后我就找到阮一峰博客里面写的关于es6块级作用域的文章:

http://es6.ruanyifeng.com/#docs/let#%E5%9D%97%E7%BA%A7%E4%BD%9C%E7%94%A8%E5%9F%9F

另一篇关于js变量的生命周期的文章:

https://dmitripavlutin.com/variables-lifecycle-and-why-let-is-not-hoisted/

在第一篇文章中,看见里面有真么一段话:

然后找到这么一句话,我就点链接进去看:

发现es6规范里面真的有对代码块作用域的一些规定,但是我也没太看懂,反正能确定跟这有关系。然后我又点击了另一个行为方式的链接进去看:

然后在这个回答里找到这么一个回答,这个回答说代码块作用域中定义的函数类似于var定义的变量,(前面阮一峰博客里也是这么说的),然而第二个绑定仅在块内部可见,也就是说,第二个绑定在外面是访问不到的,那用这段话来解释我们代码的话就是先不管代码块中的函数声明提升,然后从上面往下运行,看见第一个就绑定到全局的window对象上,第二个就只在函数作用域内可见,那这样的话我如果在代码块内部打印,那结果应该是谁在后面定义我们就打印谁啊,而打印的结果却是证明了函数提升存在的。所以这个好像也解释不通。然后从这个回答里面我又找到一个这个链接:

然后的然后我就不知道该怎么去看这个问题了,但是我相信应该接近答案了,或许答案就藏在上面es6规范B3.3里面的某个点。当然也可以从调用栈,js垃圾回收,js上下文,js引擎执行解析过程,函数与变量声明创建原理等等各个方面去分析,这应该是一个值得去分析思考的问题,同时也是很有意思的一个问题,相信你是能学到一些东西的,下面有一些参考链接,如果感兴趣可以研究一下,在平常写代码的时候可能永远也不会遇到,很有意思的问题。
参考资料:

一个有意思的js块作用域问题的更多相关文章

  1. 一个有意思的js实例,你会吗??[原创]

    首先,看看下面一个js例子,你觉得会输出什么呢? function fn(a){ a(); function a(){ console.log(2); } var a = function(){ co ...

  2. 一个有意思的js小问题

    问题:如何实现以下函数? add(2, 5); // 7 add(2)(5); // 7 第一个就不用说了,很简单,关键是看第二个,add(2)(5),可见add(2)应该返回的是一个函数,这个函数再 ...

  3. 混合开发的大趋势之 一个Android程序员眼中的 React.js 块级作用域 和 let

    转载请注明出处:王亟亟的大牛之路 最近都有事干然后,快到月底了这个月给CSDN的博文也就两篇,想想也蛮多天没更了,那就来一篇. 老规矩,先安利:https://github.com/ddwhan012 ...

  4. 读书笔记-你不知道的JS上-函数作用域与块作用域

    函数作用域 Javascript具有基于函数的作用域,每声明一个函数,都会产生一个对应的作用域. //全局作用域包含f1 function f1(a) { var b = 1; //f1作用域包含a, ...

  5. python 控制语句基础---->代码块:以为冒号作为开始,用缩进来划分作用域,代表一个整体,是一个代码块,一个文件(模块)也称为一个代码块 | 作用域:作用的范围

    # ### 代码块:以为冒号作为开始,用缩进来划分作用域,代表一个整体,是一个代码块,一个文件(模块)也称为一个代码块 # ### 作用域:作用的范围 print(11) print(12) prin ...

  6. js私有作用域(function(){})(); 模仿块级作用域

    摘自:http://outofmemory.cn/wr/?u=http%3A%2F%2Fwww.phpvar.com%2Farchives%2F3033.html js没有块级作用域,简单的例子: f ...

  7. You Don't Know JS: Scope & Closures (第3章: 函数 vs 块作用域)

    第二章,作用域由一系列的bubbles组成.每一个都代表了一个container或bucket,装着被声明的identifiers(variables, functions).这些bubbles相互嵌 ...

  8. ES6中块作用域之于for语句是怎样的?

    在ES6中新加了快作用域的概念(C语言就有,作为类c语言的js,当然应该加上),算是很好理解. { let i; } console.log(i);// i is not defined 在代码块当中 ...

  9. JS的作用域和作用域链

    每个函数都有自己的作用域,当执行流进入一个函数时,函数就会被推入栈中,而在函数执行之后,栈将其执行环境弹出,把控制权放回给之前的作用域,全局作用域是最外围的一个作用域,因此,所有全局变量和函数都是作为 ...

随机推荐

  1. 试试 IEnumerable 的 10 个小例子

    IEnumerable 接口是 C# 开发过程中非常重要的接口,对于其特性和用法的了解是十分必要的.本文将通过10个小例子,来熟悉一下其简单的用法. 全是源码 以下便是这10个小例子,响应的说明均标记 ...

  2. Redis的初识

    简介 已经有了Membercache和各种数据库,Redis为什么会产生?Redis纯粹为应用而产生,它是一个高性能的key-value数据库.Redis的出现,很大程序补偿了Memcached这类k ...

  3. 手把手教你用深度学习做物体检测(六):YOLOv2介绍

    本文接着上一篇<手把手教你用深度学习做物体检测(五):YOLOv1介绍>文章,介绍YOLOv2在v1上的改进.有些性能度量指标术语看不懂没关系,后续会有通俗易懂的关于性能度量指标的介绍文章 ...

  4. lightoj 1097 - Lucky Number(线段树)

    Lucky numbers are defined by a variation of the well-known sieve of Eratosthenes. Beginning with the ...

  5. CF1009B Minimum Ternary String 思维

    Minimum Ternary String time limit per test 1 second memory limit per test 256 megabytes input standa ...

  6. 在windows上,使用虚拟机安装苹果操作系统

    以下是我这两天安装这个苹果操作系统时,所看的文档,集合.已经成功,再次做一个摘录. 分别看了一下几个链接: http://www.bubuko.com/infodetail-2257390.html ...

  7. WoSign新证书系统通过德国Cure53安全测试

    近日,沃通WoSign新证书系统顺利通过德国Cure53白盒子安全测试,并公开发布审计报告总结版. 据悉,根据去年10月份Mozilla提出的整改要求,沃通WoSign投入研发力量高标准严要求地重新开 ...

  8. Storm 系列(一)—— Storm和流处理简介

    一.Storm 1.1 简介 Storm 是一个开源的分布式实时计算框架,可以以简单.可靠的方式进行大数据流的处理.通常用于实时分析,在线机器学习.持续计算.分布式 RPC.ETL 等场景.Storm ...

  9. Go语言基础之并发

    并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因. Go语言中的并发编程 并发与并行 并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天) ...

  10. linux常用命令三

    linux常用命令三 系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 ...