作用域与作用域链

通常来说,一段程序代码中所用到的名字并不总是有效或可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域scope,当一个方法或成员被声明,他就拥有当前的执行上下文context环境,在有具体值的context中,表达式是可见也都能够被引用,如果一个变量或者其他表达式不在当前的作用域,则将无法使用。作用域也可以根据代码层次分层,以便子作用域可以访问父作用域,通常是指沿着链式的作用域链查找,而不能从父作用域引用子作用域中的变量和引用。

作用域

JavaScript作用域为静态作用域static scope,也可以称为词法作用域lexical scope,其主要特征在于,函数作用域中遇到既不是参数也不是函数内部定义的局部变量时,去函数定义时上下文中查,而与之相对应的是动态作用域dynamic scope则不同,其函数作用域中遇到既不是参数也不是函数内部定义的局部变量时,到函数调用时的上下文中去查。

var a = 1;
var s = function(){
    console.log(a);
};

(function(){
    var a = 2;
    s(); // 1
})();

调用s()是打印的a1,此为静态作用域,也就是声明时即规定作用域,而假如是动态作用域的话在此处会打印2。现在大部分语言都采用静态作用域,比如CC++JavaPHPPython等等,具有动态作用域的语言有Emacs LispCommon LispPerl等。

全局作用域

直接声明在顶层的变量或方法就运行在全局作用域,借用函数的[[Scopes]]属性来查看作用域,[[Scopes]]是保存函数作用域链的对象,是函数的内部属性无法直接访问但是可以打印来查看。

function s(){}
console.dir(s);
/*
  ...
  [[Scopes]]: Scopes[1]
    0: Global ...
*/
// 可以看见声明的s函数运行的上下文环境是全局作用域

函数作用域

当声明一个函数后,在函数内部声明的方法或者成员的运行环境就是此函数的函数作用域

(function localContext(){
    var a = 1;
    function s(){ return a; }
    console.dir(s);
})();
/*
  ...
  [[Scopes]]: Scopes[2]
    0: Closure (localContext) {a: 1}
    1: Global ...
*/
// 可以看见声明的s函数运行的上下文环境是函数localContext的作用域,也可以称为局部作用域

块级作用域

代码块内如果存在let或者const,代码块会对这些命令声明的变量从块的开始就形成一个封闭作用域。

{
    let a = 1;
    function s(){return a;}
    console.dir(s);
    /*
      ...
      [[Scopes]]: Scopes[2]
        0: Block {a: 1}
        1: Global ...
    */
}
// 可以看见声明的s函数运行的上下文环境是Block块级作用域,也是局部作用域

作用域链

var a = 1;
(function s(){
    var a = 2;
    console.log(a); // 2
})();
console.log(a); // 1
var a = 1;
function localContext(){
    var b = 2;
    function localContext2(){
        var c = 3;
        {
            let d = 4;
            function s(){ return a + b + c + d; }
            console.dir(s);
            /*
              ...
              [[Scopes]]: Scopes[4]
                0: Block (localContext2) {d: 4}
                1: Closure (localContext2) {c: 3}
                2: Closure (localContext) {b: 2}
                3: Global ...
            */
        }
    }
    localContext2();
}
localContext();

可以通过[[Scopes]]看到s的作用域链,当我们在s中使用d时,在s中不存在d这个参数或者是局部变量,就会到[[Scopes]]中去查找,到Block作用域时查找到了,就获得了d的值,当使用cba时,也是同理在作用域链查找到localContext2作用域、localContext作用域、Global作用域,总结来说,当需要使用函数或者变量时,如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这样一个查找过程形成的链条就叫做作用域链。

相关

闭包 https://github.com/WindrunnerMax/EveryDay/blob/master/JavaScript/JavaScript%E9%97%AD%E5%8C%85.md let与const https://github.com/WindrunnerMax/EveryDay/blob/master/JavaScript/let%E4%B8%8Econst.md

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://www.jianshu.com/p/a198dce5d523 https://www.cnblogs.com/gaosirs/p/10579059.html