彻底理解js的作用域链

作者: csRyan | 来源:发表于2017-03-08 23:06 被阅读508次

之前的文章中我已经介绍了执行上下文的变量对象。在这一篇文章我要介绍执行上下文的作用域链了。

执行上下文.作用域链(scope chain)

作用域链与变量对象有着密不可分的关系,因为作用域链就是变量对象的数组!其中第一个是当前函数的活动对象,第二个是当前活动函数的父亲上下文的活动对象,第三个是当前活动函数的爷爷上下文的活动对象,……,最后一个是全局上下文的活动对象。当执行引擎在解析一个变量名的时候,会沿着当前执行函数的作用域链查找,如果在某个活动对象中找到了,则使用它。找不到则报错。

什么叫父亲上下文?这个函数在哪个上下文中被声明,那个上下文就是此函数的父亲上下文,这个函数的声明会被保存到父亲上下文的变量对象中。上层上下文(也就是所有前辈上下文)对于作用域链的创建至关重要,我们会在文章后面讲。

举个栗子:

var global_var = -1;

function outter() {
    var outter_var = 111;
    console.log(
        "in outter, global_var = " + global_var +
        ", outter_var = " + outter_var
    );

    function inner() {
        var inner_var = 333;
        console.log(
            "in inner, global_var = " + global_var +
            ", outter_var = " + outter_var +
            ", inner_var = " + inner_var
        );
    }
    inner();
}
outter();
// 输出
// in outter, global_var = -1, outter_var = 111
// in inner, global_var = -1, outter_var = 111, inner_var = 333

开始执行outter()之前,创建的执行上下文.作用域链是:
[outter的变量对象, 全局的变量对象]

  • 解析global_var的时候,在outter的变量对象中没有找到,因此在全局的变量对象中找,找到这个属性,所以可以打印出它的值-1。
  • 解析outter_var的时候,在outter的变量对象中就找到了,因此不用往后找了,直接打印111。

inner的执行上下文.作用域链是:
[inner的变量对象,outter的变量对象, 全局的变量对象]
变量解析的过程同上。

inner的父亲上下文是outter,outter的父亲上下文是全局上下文。


为什么js有函数级作用域

前一篇文章,我们解释过为什么js不是块级作用域,到这里我们又可以解释为什么js有函数级作用域了。如果你尝试在上面例子的outter()中打印inner_var,会报错。因为outter的作用域链是[outter的变量对象, 全局的变量对象],根本就没有inner的变量对象!自然也就找不到inner_var了。


作用域链是怎么创建的

虽然说作用域链是在函数执行之前,与执行上下文一起创建的,但是实际上其中很大一部分内容在定义的时候就已经确定了。

作用域链这个数组可以分成两部分:

  1. 第一个元素就是本函数的变量对象
  2. 后面的所有元素都是上层上下文的变量对象(按照由近到远的顺序排列)。在网上的一些资料中,所有上层上下文的链叫做[[Scope]]。

第一部分是在函数执行之前创建的。第二部分其实在函数声明的时候就已经确定了,[[Scope]]是函数的一个属性,从函数声明到函数销毁一直存在,且不会改变。我们在文章开头也说过,决定上层上下文的是函数在哪里声明,而不是函数在哪里调用
因此,本函数的变量对象一旦创建好,执行引擎就可以合并[[Scope]],创建出一个作用域链了。

可以用这种方式轻松地找出[[Scope]]:[[Scope]]就是函数被声明时的上下文的作用域链。

举个例子:

function inner() {
    // 创建作用域链:[inner的变量对象,全局上下文的变量对象],其中没有outter的变量对象!怪不得找到不到outter_var!
    console.log("in inner, outter_var = "+outter_var);
}
// inner声明完毕,[[scope]]已经确定
// 因为此时的上下文是全局上下文,所以[[scope]]就是全局上下文的作用域链,也就是全局变量对象

function outter() {
    var outter_var = 111;
    inner();  
}

outter(); // 报错:outter_var is not defined

如果将inner声明在outter里面,就不会报错啦:

function outter() {
    var outter_var = 111;

    function inner() {
        // 创建作用域链:[inner的变量对象,outter的变量对象,全局上下文的变量对象]
        console.log("in inner, outter_var = " + outter_var);
    }
// 声明完毕,[[scope]]已经确定
// 因为此时的上下文是:outter上下文
// outter上下文的作用域链是:[outter上下文的变量对象,全局上下文的变量对象]
// 所以[[scope]]是:[outter上下文的变量对象,全局上下文的变量对象]
    inner();
}

outter();
//  输出:in inner, outter_var = 111

解释闭包的原理

如果看过我之前的文章彻底理解js闭包现在应该已经能掌握闭包的使用方法了。是时候给闭包最后一击了!我们接下来要用作用域链来解释闭包的原理!
看看我在“彻底理解js闭包”中举的一个栗子:

function outter() {
  var private= "I am private";
  function show() {
    console.log(private);
  }
// [[scope]]已经确定:[outter上下文的变量对象,全局上下文变量对象]
  return show;
}

var ref = outter();
// console.log(private);  // 尝试直接访问private会报错:private is not defined
ref(); // 打印I am private

outter执行完以后,private不会被销毁,并且只能被show方法所访问,而尝试直接访问它会出现报错。

  • 首先我们解释为什么不能直接从外部访问它:
    执行到报错的那一行的时候,当前上下文的作用域链是:[全局上下文变量对象],其中的变量对象中并没有private(private在outter的上下文变量对象中)!这就是为什么我们不能直接访问private
  • 然后我们解释为什么能够通过show来访问private
    调用ref(也就是show)的时候,因为show的[[scope]]是:
    [outter变量对象,全局变量对象]
    所以创建的作用域链是:
    [show变量对象,outter变量对象,全局变量对象]
    查找private的时候,在outter变量对象中找到了,所以可以访问到。

可见,闭包之所以能够“记忆”它被创建时候的环境,就是因为[[scope]]将函数声明时所在上下文的作用域链给存起来了!


有关this指针是如何确定的,请看这一篇文章

相关文章

  • 彻底理解js的作用域链

    在之前的文章中我已经介绍了执行上下文的变量对象。在这一篇文章我要介绍执行上下文的作用域链了。 执行上下文.作用域链...

  • JavaScript 函数闭包(colsure)

    理解闭包,你首先必须理解JS的变量作用域,JavaScript作用域和作用域链。 ES6之前,变量的作用域分为全局...

  • 闭包

    一、理解闭包前js基础1、作用域链(作用域、作用域链中有说)。2、js的内存回收机制。一个函数在执行开始的时候,会...

  • JS博客

    JS构造函数及new运算符 JS原型对象和原型链 函数作用域和作用域链 干货分享:让你分分钟学会JS闭包 深入理解...

  • 《Web前端开发之JavaScript精英课堂》(六)

    对 作用域、作用域链、执行上下文对象(GO | AO)、闭包 的个人理解: JS分为全局和局部作用域,都属于词法作...

  • 你不知道的JS-上卷

    作用域整体理解:JS作用域分为函数作用域,全局作用域,with和try catch形成的块级作用域。 JS引擎在编...

  • 干货!月薪80k前端大佬面试笔记:JS闭包解析!

    三点注意事项 JS没有块级作用域,只有全局作用域和局部作用域(函数作用域)。 JS中的作用域链,内部的作用域可以访...

  • JS 作用域链、导入导出

    1. JS 的作用域链 作用域在 JS 中表示变量的可访问性和可见性。JS 作用域有 3 种:1. 全局作用域;2...

  • js 闭包

    一、js 作用域 讲闭包首先就要理解 js 的作用域。再 ES5 中,js 有两种作用域,全局作用域和函数作用域(...

  • 彻底搞懂JS作用域和作用域链

    作用域和作用域链 作用域:决定了代码区块中变量和其他资源的可见性; ·全局作用域和函数作用域 作用域是分层的内部可...

网友评论

本文标题:彻底理解js的作用域链

本文链接:https://www.haomeiwen.com/subject/hovbgttx.html