JavaScript 之做用域与做用域链

首发地址: https://github.com/jeuino/Blo...

概述

在上一篇《JavaScript 之执行上下文》中介绍了什么是执行上下文与执行上下文栈,本篇文章主要总结了:javascript

  • 什么是做用域?
  • 什么是词法做用域和动态做用域?它们的区别是什么?
  • JavaScript 采用了什么类型的做用域?
  • JavaScript 中做用域的类型?
  • 执行上下文中的做用域链?

做用域 Scope

《 JavaScript 引擎(V8)是如何工做的》中说到过,做用域是由 V8 的 Parser 解析器肯定的。那么什么是做用域呢,咱们下面来聊一聊。java

在软件设计中,有一个公共的原则——最小受权(暴露)原则。这个原则是指在软件设计中, 应该最小限度地暴露必要内容, 而将其余内容都“隐藏” 起来。git

这样作的优势是:github

  1. 能够下降多文件引入时,变量或函数命名出现冲突的几率;
  2. 若是将全部内容都暴露给全局环境,那么会占用不少无用内存,只有当关掉浏览器或当前窗口时,全局变量才会被回收;
  3. 若是程序出现错误,能够更小范围的肯定出错区域;

在 JavaScript 中就是经过做用域来实现最小受权原则的。编程

做用域规定了变量和函数的可访问性。浏览器

做用域实施了一套严格的规则,用于规定在 JavaScript 运行时如何查找变量和函数,也就是肯定当前执行代码对变量和函数的访问权限。bash

做用域共有两种主要的工做模型。 第一种是最为广泛的, 被大多数编程语言所采用的词法做用域。 另一种叫做动态做用域session

JavaScript 采用的是词法做用域,也称为静态做用域。编程语言

词法做用域

词法做用域是由你在写代码时变量和函数声明的位置来决定的。函数

词法做用域的“父子关系”,取决于代码书写时的嵌套关系。

咱们来分析下面这段代码:

var a = 1;

// foo 函数声明在全局执行上下文中
function foo() {
    console.log(a);
}
// bar 函数声明在全局执行上下文中
function bar() {
    var a = 2;
    foo();
}

bar();

Image  2

这里补充一个小知识,就是上图描述文字中出现的 RHS 是什么意思,这涉及到了 JavaScript 执行过程当中引擎是如何查找变量的。

引擎在执行代码时,会经过查找标识符来判断它是否已经声明过。查找的过程由做用域进行协助,可是引擎是怎么查找的呢?引擎查找变量有两种方式,分别是:

  • LHS 查询

若是查找的目的是对变量进行赋值,则使用 LHS 查询(告诉做用域我须要对 a 变量进行 LHS 引用,你见过它嘛?)
不成功的 LHS 引用会致使自动隐式建立一个全局变量(非严格模式),严格模式下抛出ReferenceError 异常

  • RHS 查询

若是查找的目的是获取变量的值,则使用 RHS 查询(告诉做用域我须要对 a 变量进行 RHS 引用,你见过它嘛?)
不成功的 RHS 查询会抛出 ReferenceError 异常,不会隐式建立一个全局变量。

请看下面这个例子,其中 RHS 共使用了三次,LHS 共使用了两次,你能找到都是在哪里使用了 RHS 和 LHS 吗?

function add(a, b) {
    return a + b;
}
add(1, 2)

好了,咱们再回到词法做用域上。看了上述分析,可能你仍是不太明白什么是词法做用域,下面咱们再来看下什么是动态做用域,经过与动态做用域进行对比,你应该会有一个更清晰的认知。

动态做用域

词法做用域是在写代码或者说定义时肯定的,而动态做用域是在运行时肯定的。

动态做用域不关心函数和变量是在何处声明的,只关心它们是从何处调用的;

动态做用域是基于调用栈 的,而不是代码中的做用域嵌套。

咱们从动态做用域的角度,再来分析上面那段代码:

var a = 1;

function foo() {
    console.log(a);
}

function bar() {
    var a = 2;
    // 在 bar 函数内调用 foo 
    foo();
}

bar(); // 1

Image  6

若是 bar 函数中也没有找到 a,则会顺着调用栈到全局环境中查找,此时输出结果为 1。

bash 采用了动态做用域

如今应该对这两个概念有个清晰的认知了吧。

JavaScript 中的做用域

JavaScript 中的做用域类型分为:

  • 全局做用域(Global Scope)
  • 局部做用域(Local Scope)

    • 函数做用域
    • 块级做用域(ES6)

全局做用域

全局做用域就是最顶层的做用域,只有一个,而且能够由程序中的任何函数访问。
在 JavaScript 中,如下两种状况声明的变量和函数会处于全局做用域内:

  • 在全局执行上下文中定义的变量和函数是全局范围的;
var a = 1; // window.a

function foo() {} // window.foo
  • 未定义直接赋值的变量(非严格模式下)
function foo () {
    a = 1; // window.a
}

全局做用域中的数据,均可以经过 window 对象的属性来访问。

局部做用域

函数做用域

每一个函数都有本身的做用域。函数做用域有权访问全局做用域,反之不行。

// Global Scope
function fn() {
    // Local Scope #1
    function someOtherFunction() {
        // Local Scope #2
    }
}

块级做用域

块做用域是 ES6 的新特性,它指的是变量不只能够属于所处的做用域,也能够属于某个代码块( { .. } 内部)。

只有使用 let 和 const 关键字声明的变量才会产生块级做用域。

if (true) {
    // if 条件语句不会建立一个做用域 

    // a 处于全局做用域中
    var a = 'a';
    // b 处于当前块级做用域内
    let b = 'b';
    // c 也处于当前块级做用域内
    const c = 'c';
}

console.log(a); // a
console.log(b); // Uncaught ReferenceError: b is not defined
console.log(c); // Uncaught ReferenceError: c is not defined

做用域链

咱们都知道,局部做用域有权访问自身做用域和全局做用域;若是一个函数内部嵌套了一个函数,则嵌套的函数也是有权访问自身做用域、声明所在函数做用域以及全局做用域的。

每一个做用域都存在一条由可访问的做用域造成的做用域链(Scope Chain)。

举个例子:

var a = 1

function foo () {
    var b = a + 1;

    function bar () {
        var c = b + a
    }
    bar ()
}

foo ()

当咱们开始执行上述代码时,首先会建立一个全局执行上下文。在上一篇《JavaScript 之执行上下文》文尾,咱们说明了,每一个执行上下文都包含三个重要的属性:

  • 变量对象(Variable Object,VO)
  • 做用域链(Scope Chain)
  • this指向
这里咱们先不关注变量对象和 this,后面会有单独的文章进行介绍。

执行上下文的伪代码能够表示以下:

global_EC = {
    scopeChain: {
        //  current scope + scopes of all its parents
        global_scope
    },
    variableObject: {
        // All the variables including inner variables & functions, function arguments
    },
    this: {}
}

在做用域链(scopeChain)中按照"从大到小"的顺序依次存放着当前做用域和它的全部父级做用域。
在全局执行上下文中,它的做用域链只包含一个做用域,即全局做用域。

scopeChain = [global_scope]

当执行 foo 函数时,foo 执行上下文的做用域链以下所示:

scopeChain = [global_scope, foo_scope]

当执行 bar 函数时,bar 执行上下文的做用域链以下所示:

scopeChain = [global_scope, foo_scope, bar_scope]

做用域链的查询:
当解释器在执行代码遇到一个变量时,它首先会在当前做用域内查找其值;若是找不到,它会遍历做用域链,继续从上一级做用域查找;依此类推,直到找到变量或到达做用域链的末尾(全局做用域)时结束。

下一篇

原来下篇文章是想写执行上下文中的变量对象的,可是想在介绍变量和函数是如何引用的以前,先总结一下它们是如何存储的。因此调整了一下发文顺序。

传送门:《JavaScript 以内存空间》

参考:

JavaScript深刻之词法做用域和动态做用域
How JavaScript works: Parsing, Abstract Syntax Trees (ASTs) + 5 tips on how to minimize parse time
Understanding Scope and Scope Chain in JavaScript
Understanding Scope in JavaScript
相关文章
相关标签/搜索