漫谈JavaScript中的做用域(scope)

什么是做用域

程序的执行,离不开做用域,也必须在做用域中才能将代码正确的执行。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中的块级做用域究竟是什么?

let & const

利用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'才会入栈;

JavaScript代码执行方式

执行doSomething的时候,还未执行let name = 'otherName',因此,此时doSomething的词法环境中并未有name = 'otherName',这个时候查找,只能向外部做用域查找(全局做用域)

此时查找到全局做用域name = 'someName'因此此时就打印了someName

代码接着执行到了if语句内部,才会将name  = 'otherName'入栈,可是此时由于语句已经执行完毕,因此也就无关痛痒了。

JavaScript也就经过这种方式,实现了块级别做用域。

总结

JavaScript中的做用域总的来讲,分为块级做用域、函数做用域、全局做用域。

而每一个做用域都会建立自身的执行上下文,每个执行上下文又分为了变量环境和词法环境两部分。

块级做用域的实现,实际上是根据定义的let和const声明的变量放置在词法环境栈中这一特性来实现。

这一特性被社区的人叫作‘暂时性死区’,可是在JavaScript标准中并未有这个概念。

只有理解了做用域的概念,才能真正明白JavaScript的执行机制,才能减小咱们由于变量定义等发生的错误。

个人博客:http://www.gaoyunjiao.fun/?p=148

相关文章
相关标签/搜索