执行上下文和作用域,作用域链

  • 给执行上下文环境下一个通俗的定义——在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。(变量的值是在执行过程中产生的确定的)
  •  javascript除了全局作用域之外,只有函数对象不创建作用域可以创建的作用域。(ES6新加了块级作用域 let)

 

全局代码的上下文环境数据内容为:

 

普通变量(包括函数表达式),

如: var a = 10;

声明(默认赋值为undefined

函数声明,

如: function fn() { }

赋值

this

赋值

 

如果代码段是函数体,那么在此基础上需要附加:

 

参数

赋值

arguments

赋值

自由变量的取值作用域

赋值

作用域在函数定义时就已经确定了,而不是在函数调用时确定。作用域有上下级的关系,上下级关系的确定就看函数是在哪个作用域下创建的。例如,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

 

 

 

作用域只是一个“地盘”,一个抽象的概念,其中没有变量。要通过作用域对应的执行上下文环境来获取变量的值。

 

一个例子:

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log( i);
    }, 1000);
}

console.log(i);

如果我们约定,用箭头表示其前后的两次输出之间有 1 秒的时间间隔,而逗号表示其前后的两次输出之间的时间间隔可以忽略,代码实际运行的结果该如何描述?

结果是5->5,5,5,5,5。因为i是个自由变量,当循环结束时,i已经等于5了,而setTimeout中的函数还未执行,等其执行时,获取的i为5。

若想输出5->0,1,2,3,4,利用 JS 中基本类型(Primitive Type)的参数传递是按值传递(Pass by Value)的特征即可。

方法一:IIFE

for (var i = 0; i < 5; i++) {
    (function(j) {  // j = i
        setTimeout(function() {
            console.log(j);
        }, 1000);
    })(i);
}

console.log( i);

方法二:

 

var output = function (i) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
};

for (var i = 0; i < 5; i++) {
    output(i);  // 这里传过去的 i 值被复制了
}

console.log(i);

 

自由变量:

在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。如下图 

如上程序中,在调用fn()函数时,函数体中第6行。取b的值就直接可以在fn作用域中取,因为b就是在这里定义的。而取x的值时,就需要到另一个作用域中取。到哪个作用域中取呢?

要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,—其实这就是所谓的“静态作用域”。

作用域链:

 

上面描述的只是跨一步作用域去寻找。

 

如果跨了一步,还没找到呢?——接着跨!——一直跨到全局作用域为止。要是在全局作用域中都没有找到,那就是真的没有了。

 

这个一步一步“跨”的路线,我们称之为——作用域链。

我们拿文字总结一下取自由变量时的这个“作用域链”过程:(假设a是自由量)

第一步,现在当前作用域查找a,如果有则获取并结束。如果没有则继续;

第二步,如果当前作用域是全局作用域,则证明a未定义,结束;否则继续;

第三步,(不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;

第四步,跳转到第一步。

 

posted @ 2017-12-11 13:38  白十三  阅读(539)  评论(0编辑  收藏  举报