zoukankan      html  css  js  c++  java
  • JavaScript作用域链详解

    JavaScript的作用域链还是很有味道的,搞懂了这个知识点,闭包的问题也就迎刃而解咯

    1、JavaScript的全局变量和局部变量

      首先,先来看看js的全局变量和局部变量,js不是块级作用域,所以不能把你学过的C/C++作用域的知识用在js中!

    (1)全局变量

      js的全局变量也可以看做window对象的属性,这句话怎么理解,请看以下代码:

    var x = 10;
    alert(window.x);//弹出10

      也就是说var x = 10;等价于window.x=10;

      再来看一段代码

    function foo(){
        x = 10;
    }
    foo();
    alert(window.x);

      这会弹出什么呢?answer is 10!

      如果在函数中定义变量时没有用关键字var,那么实际上定义的就是全局变量。你经常会看到前端优化的一个点:尽量少定义全局变量!如果不可避免的用到全局变量,那么就在局部变量中保存。像这样:

    function foo(){
        var doc = document;
        var divObj = doc.getElementByTagName('div');
    }

      把document对象保存在局部变量doc中,然后对doc进行操作。

      但是,问题来了,为什么这样能提高效率呢?这个问题我们先留着,等讲完作用域链再来看。

    (2)局部变量

      要说块级作用域,那么在js中就只有函数块,函数中定义的变量就是局部变量,当然必须有关键字var!(没有关键字var定义的都是全局变量)

    也就是说if else语句和for循环中创建的变量在外部都可以访问的到

    function foo(){
        var x = 1;
    }
    for(var i = 0;i<10;i++){
        
    }
    if(i){
        var y = 10;
    }
    foo();
    alert(i);//10
    alert(y);//10
    alert(x);//error x is not defined

    2、作用域链

      这是重点咯,什么是作用域链,还是通过代码来解释

    var x = 1;
    function foo(){
        var y = 2;
        
        function bar(){
            var z = 3;
            alert(x+y+z);
        }
        bar();
    }
    foo();

    答案是几不用说吧,在bar函数中没有y和z,执行x+y+z时,js搜索x,y,z变量的一种机制就是作用域链,这个例子的搜索顺序:bar->foo->window

    前面讲的太简单,可能已经有人看不下去了,来点干货吧

    bar的作用域链是:

    barScopeChain = [
        bar.AO,
        foo.AO,
        global.VO
    ];

    foo的作用域链是:

    fooScopeChain = [
        foo.AO,
        global.VO
    ];

    可能各位看官都会迷糊,可能会问,这个AO,VO,是个什么玩意儿?我们慢慢来,先来看看变量bar函数变量搜寻过程

    例如:找x变量;bar函数在搜寻变量x的过程中,先从自身AO对象上找,如果bar.AO存在这个属性,那么会直接使用这个属性的值,如果不存在,则会转到父级函数的AO对象,也就是foo.AO

    如果找到x属性则使用,找不到继续 在global.VO对象查找,找到x的属性,返回属性值。如果在global.VO中没有找到,则会抛出异常ReferenceError。

      在函数执行过程中,每遇到一个变量,都会检索从哪里获取和存储数据,该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没有则继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义,函数执行过程中,每个标识符都要经历这样的搜索过程。

    知道了这一点,回过头看刚开始的那个问题,为什么要少定义全局变量,从而进行优化?

    因为作用域链是栈的结构,全局变量在栈底,每次访问全局变量都会遍历一次栈,这样肯定会影响效率。

    在函数创建时,每个函数都会创建一个活动对象Active Object(AO),全局对象为Global Object(VO),创建函数的过程也就是为这个对象添加属性的过程,作用域链就是由这些绑定了属性的活动对象构成的。

      在函数执行的过程中,会创建函数的执行上下文,这里就不做过多解释,可以去看Tom大叔的深入理解JavaScript系列

      执行上下文是一个动态的概念,当函数运行的时候创建,活动对象 Active Object 也是一个动态的概念,它是被执行上下文的作用域链引用的,可以得出结论:执行上下文和活动对象都是动态概念,并且执行上下文的作用域链是由函数作用域链初始化的。PS:这些概念都是js引擎解析代码的内部机制,外部是无法访问的!

      

      还是刚才那段代码,我们来看看js引擎编译的过程,进一步了解具体是怎么创建作用域链的

      函数进入全局,创建VO对象,绑定x属性<入栈>(这里只是预解析,为ao对象绑定声明的属性,函数执行时才会执行赋值语句,所以值是underfind)

    global.VO = {
       x:underfind;
       foo:reference of function 
    }

      遇到foo函数,创建foo.VO,绑定y属性<入栈>

    foo.AO = {
       y:undefined;
       fbar:reference of function 
    }

      接下来是bar函数,z属性<入栈>

    bar.AO = {
       z:undefined;
    }

    作用域链和执行上下文都会保存在堆栈中,所以

    bar函数的scope chain为[0]bar.AO-->[1]foo.AO-->[2]global.VO

    foo函数的scope chain为[0]foo.AO-->[1]global.VO

    这里有一个等式:scope=AO|VO + [[scope]]

    函数scope等于自身的AO对象加上父级的scope,也可以理解为一个函数的作用域等于自身活动对象加上父级作用域。

    3、来看一个闭包的例子

    我的本意是给每个li标签绑定一个点击事件,点击后弹出对应的索引,单实际上每次都会弹出“这是第四个li标签”

    分析:在创建foo函数时,foo.AO={liObj:undefined,i:undefined,onclick:refeerence of function}

    在函数执行中,这个匿名函数自身没有i这个变量,所以会到foo的活动对象中找i变量,此时for循环已执行,变量i的值已经改变,所以总是会输出4

    那怎么解决这个问题呢?也很简单

    第一种解决方法:

    用一个函数来保存变量i即可

    第二种解决方法:

    细心的人绝对会发现其实是一种方法,都是把全局变量放在局部保存

    最后一个点:变量查找时原型链是优先于作用域链的。js引擎先在函数AO对象查找,再到原型链查找,接着是作用域链。

    还没写完,今天实在没什么感觉,脑子很乱,还是出去走走,透透气吧,有不对的地方还希望好心人指出。

  • 相关阅读:
    HDU 6984
    洛谷 P6776
    C语言 error C4996: This function or variable may be unsafe
    C语言 sizeof 函数
    C语言 strlen 函数
    C语言 char 字符串
    C语言 goto 语句
    C语言 switch 语句
    C语言 do while 和 while 循环
    C语言 while 循环
  • 原文地址:https://www.cnblogs.com/jianjianwoshi/p/4541007.html
Copyright © 2011-2022 走看看