作用域和作用域链

什么是作用域?

作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

具体分类:

名称 说明
window/global Scope 全局作用域
function Scope 函数作用域
Block Scope 块作用域(ES6)
eval Scope eval作用域(已经弃用)

示例代码:

// 全局作用域
var a = 'andy';
function test() {
    // 局部作用域
    var b = "Tim";
    console.log(b);
}
test(); // 输出 'Tim'
console.log(a); // 输出 'andy'


if (true) {
    // 这个 'if' 块语句没有创建一个块级作用域

    // name 变量处于全局作用域,因为由var关键字声明
    var name = 'Hammad';
    // likes 变量处于块级作用域因为由let关键字声明
    let likes = 'Coding';
    // skills 变量处于块级作用域因为由const关键字声明
    const skills = 'JavaScript and PHP';
}

console.log(Hammad); // 输出 'Hammad'
console.log(likes); // Uncaught ReferenceError: likes is not defined
console.log(skills); // Uncaught ReferenceError: skills is not defined

理解前提

  • function的AO理解为“独立的仓库(作用域)”
  • 原理:利用AO、GO来解决作用域、作用域链相关所产生的一切问题
  • 函数也是一种对象类型,一种引用类型,有引用值
  • 函数有属性:test.name test.length test.prototype
  • 对象的有些属性是我们无法访问的,这些属性就是JS引擎内部固有的隐式属性(内部私有属性)
  • AO:函数的执行期上下文
  • GO:全局的执行期上下文

原理说明

(1)作用域:[[scope]](隐式属性)

  • 1、函数创建时,生成的一个JS内部的隐式属性。
  • 2、函数存储作用域链的容器,作用域链中存储AO、GO。
  • 3、当函数执行完成以后,旧的AO就销毁,当重新执行时,会重新生成新的AO。所以,AO是一个即时的存储容器。

(2)作用域链
把AO、GO从上到下排列起来,形成链式关系就是作用域链(scope chain)。

执行过程

(1)示例代码

function a() {
    function b() {
        var b = 2; 
    }
    var a = 1;
    b();
}
var c = 3;
a();

(2)图解
当a函数被定义时:

当a函数被执行时(前一刻):

当b函数被定义时:

当b函数被执行时(前一刻):

当b函数被执行结束后:

回归b函数被定义时的状态:

当a函数被执行结束时:

回归a函数被定义时的状态:

(3)总结-作用域的基础过程

  • 1、全局执行前一刻生成GO,同时函数声明已经定义
  • 2、全局执行(赋值),因为赋值是在执行的时候触发的。比如函数表达式赋值
  • 3、永远都是上级执行,下级被定义:全局执行的时候,全局函数已经被定义,全局函数执行的时候,内部的函数被定义
  • 4、当函数被定义的时候,已经形成了作用域[[scope]]、作用域链(scope chain),并放入GO
  • 5、当函数在执行的那一刻才会生成自己的AO
  • 6、函数在被定义的时候拿的是上级的作用域环境
  • 7、当函数执行完成以后,旧的AO就销毁,当重新执行时,会重新生成新的AO

(4)结论

  • 1、每一个函数在的作用域上都有GO
  • 2、函数的AO存在作用域链的最顶端
  • 3、每个函数都有一个AO和GO,并且AO在GO的上面

示例代码二

function a () {
    function b () {
        function c () {

        }
        c();
    }
    b();
}
a();

执行过程分析: