程序的执行,离不开做用域,也必须在做用域中才能将代码正确的执行。javascript
因此做用域究竟是什么,通俗的说,能够这样理解:做用域就是定义变量的位置,是变量和函数的可访问范围,控制着变量和函数的可见性和生命周期。java
而JavaScript中的做用域,在ES6以前和ES6以后,有两种不一样的状况。函数
ES6以前,JavaScript做用域有两种:函数做用域和全局做用域。设计
ES6以后,JavaScript新增了块级做用域。对象
在JavaScript变量提高的讨论中,咱们实际上是缺乏了一个做用域的概念的,变量提高其实也是针对在同一做用域中的代码来讲的。blog
对编译器的了解,让咱们明白,对于一段代码【var a = 10】变量的赋值操做,实际上是包含了两个过程:继承
一、变量的声明和隐式赋值(var a = undefined),这个阶段在编译时生命周期
二、变量的赋值(a = 10),这个阶段在运行时ip
先看一下以下代码:作用域
var flag = true; if(flag) { var someStr = 'flag is true'; } function doSomething() { var someStr = 'in doSomething'; var otherStr = 'some other string'; console.log(someStr); console.log(flag); } doSomething(); for(var i = 0; i < 10; i++) { console.log(i); } console.log(i); { var place = 'i do not want to be visited'; }
那么这一些代码在编译以后,执行以前,根据变量提高的机制,咱们能够知道应该是下面这个样子:
function doSomething() { // 函数优先提高 // 提高隐式赋值 var someStr = undefined; var otherStr = undefined; someStr = 'in doSomething'; otherStr = 'some other string'; console.log(someStr); console.log(flag); } // 隐式赋值和提高 var flag = undefined; var someStr = undefined; var i = undefined; var place = undefined; flag = true; if(flag) { someStr = 'flag is true'; } for(i = 0; i < 10; i++) { console.log(i); } doSomething(); console.log(i); { place = 'i do not want to be visited'; }
由于变量的提高特性,以及无块级做用域的概念,因此代码中在同一个做用域中变量和函数的定义,在编译阶段都会提高到顶部。
经过上述代码,咱们大致上能够得出做用域的特性:
第1、内部做用域和外部做用域是嵌套关系。外部做用域彻底包含内部做用域。
第2、内部做用域可访问外部做用域的变量,可是外部做用域不能访问内部做用域的变量,(链式继承,向上部做用域查找)。
第3、变量提高是在同一个做用域内部出现的。
第4、做用域用于编译器在编译代码时候,肯定变量和函数声明的位置。
上述代码,在ES6+的环境中运行,也是和ES6以前是相同的结果,可是ES6不是引用了块级做用域吗,为何大括号块内的代码仍是会出现和以前同样的编译方式呢?
那么,ES6中的块级做用域究竟是什么?
利用var定义的变量,具备提高的性质,可能会影响代码的执行结果。
这是var定义变量的缺陷,那么如何规避这种缺陷呢?在ES6中,设计出来了let和const来从新定变量。
可是,因为JavaScript标准定义的很是早,1995年5月JavaScript方案定义,1996年微软提供了JavaScript解决方案JScript。而网景公司为了同微软竞争,神情了JavaScript标准,因而,1997年6月第一个国际标准ECMA-262便颁布了。
C语言标准化的过程倒是将近二十年后才颁布。
因此,咱们之后设计的语言既要兼容var也要有本身的块级做用域,让var和let以及const在引擎作到兼容。
因此,咱们定义块级做用域的标准,只能从定义变量的方式入手,而不是直接一个{}块就能够解决。
先让咱们看一下下面代码:
var name = 'someName'; function doSomething(){ console.log(name); if(true) { var name = 'otherName'; } } doSomething(); 结果:undefined
产生这个结果的缘由是咱们函数内部的变量提高,覆盖了外部做用域的变量,也就是说,其实打印出来的值是doSomething函数中的变量声明的值。
可是这样却并不符合块级做用域的预期,若是有许多相似代码,理解起来也会至关困难。若是将代码用ES6方式改写:
let name = 'someName'; function doSomething(){ console.log(name); if(true) { let name = 'otherName'; } } doSomething(); 结果:'someName'
从运行结果看,咱们真正的作到了块级做用域应该有的效果,那么let和const又是如何支持块做用域的呢?
先想一想一下JavaScript中的一个做用域两个执行上下文中的编译过程当中的环境:
变量环境:编译阶段var声明存放的位置(一个大对象)。
词法环境:咱们代码书写的位置,也是let和const的初始化位置(代码按词法环境顺序执行,按照{}划分的栈结构)。
而在编译阶段,咱们将var定义的变量全都在编译过程在变量环境初始化为undefined,可是用let和const定义的变量,其实他们并未在变量环境初始化,而是在词法环境初始化,也就是执行代码位置初始化。
词法环境的特色:按照{}划分的一个栈结构。
JavaScript中变量查找的方式:沿着词法环境的栈顶向下查找,找不到的变量去变量环境中查找,这样就造成了先查找代码块中的变量,再查找提高以后的变量环境,这样就造成了块级做用域的概念。
上面的代码造成两种环境的状况以下:
1、全局环境的执行上下文
变量环境:函数声明function doSomething() { ... }
词法环境栈:执行到let name = 'someName';让语句name = 'someName'入栈。
2、doSomething的执行上下文(被全局环境包裹)
变量环境:无
词法环境栈状况:执行到let name = 'otherName',语句的时候,name = 'other'才会入栈;
执行doSomething的时候,还未执行let name = 'otherName',因此,此时doSomething的词法环境中并未有name = 'otherName',这个时候查找,只能向外部做用域查找(全局做用域)
此时查找到全局做用域name = 'someName'因此此时就打印了someName
代码接着执行到了if语句内部,才会将name = 'otherName'入栈,可是此时由于语句已经执行完毕,因此也就无关痛痒了。
JavaScript也就经过这种方式,实现了块级别做用域。
JavaScript中的做用域总的来讲,分为块级做用域、函数做用域、全局做用域。
而每一个做用域都会建立自身的执行上下文,每个执行上下文又分为了变量环境和词法环境两部分。
块级做用域的实现,实际上是根据定义的let和const声明的变量放置在词法环境栈中这一特性来实现。
这一特性被社区的人叫作‘暂时性死区’,可是在JavaScript标准中并未有这个概念。
只有理解了做用域的概念,才能真正明白JavaScript的执行机制,才能减小咱们由于变量定义等发生的错误。