执行上下文和作用域,作用域链
- 给执行上下文环境下一个通俗的定义——在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用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未定义,结束;否则继续;
第三步,(不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;
第四步,跳转到第一步。