js的作用域与作用域链

400 阅读3分钟

作用域(Scope)

作用域规定了代码中变量与函数的可访问权限,是一套规则,js采用词法作用域,也就是静态作用域。

js中作用域可分为全局作用域和函数作用域(es6有块级作用域)。

全局作用域

当你在最外层编写代码时候,就已经处于全局作用域了,在全局作用域中声明的变量,作用于全局。

如何定义全局变量

 // 1最外层定义的变量
 var a = 1;
 // 2没有通过var声明的变量
 funciotn b () {
    b = 2;
 }
 // 3挂载在window全局对象下的属性或方法
 window.c = 3;

全局变量可在任何其它作用域内访问和修改

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

函数作用域

除了全局作用域之外,每个函数都会创建自己的作用域,函数内定义的变量只能在该函数作用域内使用,这样不同作用域下同名变量不会有冲突。

静态作用域与动态作用域

静态作用域就是作用域在函数定义的时候就确定了,而不是在调用的时候确定的这点非常重要,函数的作用域基于函数创建的位置。

动态作用域是在函数调用的时候才确定。

js采用的是静态作用域,这里动态作用域就不讨论了。

看个列子

var a = 1;
function funa () {
    console.log(a);
}
function funb () {
    var a = 2;
    funa ();
}

funb(); // 输出什么?
var a = 1;
function funa () {
    console.log(a);
}
function funb () {
    a = 2;
    funa ();
}

funb(); // 输出什么?

当 funa, funb函数定义的时候,作用域就确定了;当funa调用时候就会先从函数内部查找变量a,没找到,接着就会在funa定义的位置往外层查找变量a; 找到变量a = 1; 打印出来;

作用域链(ScopeChain)

在js内部列如函数中查找一个变量的时候,会先从当前执行上下文中的变量对象中查找,如果没找到,就会从父级(函数创建时候的父级)的执行上下文的变量对象中查找,如果没找到,会一直找到全局上下文的变量对象也就是全局对象。这样由当前的执行上下文的变量对象和父级上下文的变量对象构成的链表就是作用域链。

一个完整的作用域链是在函数调用的时候形成,而作用域是函数定义的时候就确定了。

函数创建

每当一个函数创建的时候,该函数就会创建一个属性[[Scopes]]存储了所有父执行上下文的变量对象的层级链,但是[[Scopes]]不代表完整的作用域链。

function aaa () {
    var a = 0;
    function bbb () {
        console.log(a);
    }
    console.dir(bbb);
}
aaa();
// 各自的[[Scope]]
aaa.[[Scope]] = [window];
aaa.[[Scope]] = [aaaContex.AO, window];

函数执行

当函数执行,进入函数上下文,创建变量对象,然后将这个变量对象添加至[[Scope]]的前端; 这时候执行上下文中的作用域链,我命名为ScopeChain

ScopeChain = AO.concat([[Scope]]);

至此,执行上下文中的作用域链创建完成。

作用链的前端始终是当前执行上下文中的变量对象,往后是父级执行上下文的变量对象,直到最外层全局上下文的变量对象,js中的变量查找就是沿着这条链一级一级的查找,查找始终是从当前执行上下文的变量对象开始的单向查找。