• JavaScript学习日志(一):变量,作用域和内存问题


    一,变量分为两种类型:基本类型值和引用类型值,基本类型包括:Undefined, String, Boolean, Null, Number,我们无法给基本类型值添加属性;

    犀牛书上给的定义是,数据类型分两类:原始类型和对象类型,原始类型包括Number,String,Boolean,还有两个原始值,Null和Undefined。

     

    二,复制变量值的时候,如果改变复制的值,那么原来的值是否会变化?

    这个取决于原来的值是什么类型:
    1.基本类型值:只是存在于栈内存中,如果复制,相当于栈内存里又多了一个值,两者相互独立,改变其中一个,另一个不变
    2.引用类型值:实际上是指向堆内存中的一个object,复制相当于多了一个指针,也是指向这个堆内存中的一小块object,改变其中一个,所指的这个object也发生改变(注意,这个object不是整个大的Object对象,而只是堆内存其中的一小个),那么所有指向该object的值全部发生变化。

     

    三,传递参数的时候,如果参数在局部作用域下发生变化,那么参数会变化吗?
    js中,所有的参数传递都是按值传递,不管是基本类型还是引用类型。不同的类型下不同的讨论方式:
    1.基本类型值:将参数传递给函数执行,实际上就是复制了一个值,给了函数,即基本类型值的复制,局部作用域怎么变化,原值都不会变。
    2.引用类型值:将引用类型值作为参数传递给函数,实际上相当于把这个值在内存中的地址作为一个值传递给了函数,那么此时在函数里改变这个复制的地址,原来的值一定会发生改变,因为两个值指向的是同一个地址,但是如果此时在函数内部再声明一个新的引用类型值,name一样,此时这个指向的object和刚刚那两个指向的就不一样了,再次修改这个值,原来的相同name的值不会发生改变了,因为它是按值传递的。改变函数内部的参数值,外部参数也会发生改变,看起来好像是按引用传递,实际上却不是,只是因为二者引用的是同一个object,因为类型是引用类型。

    附上一堆代码:

    function setName(obj){
    
      obj.name="ABC";
    
      obj=new Object();
    
      obj.name="BCD";
    
    }
    
    var person=new Object();
    
    setName(person);
    
    alert(person.name);// ABC



    四,执行环境以及作用域的问题:
           执行环境定义了变量或函数有权访问的其他数据(这里的变量或函数可能是外部环境的,也可能是当前执行环境的)。每个执行环境有一个对应的变量对象,这里面存储了在该环境下定义的所有变量和函数(在犀牛书上定义的是当这个函数被调用的时候,它会创建一个新的对象来存储它的局部变量,这里我理解的这个创建的新的对象就是变量对象)。执行环境中所有的代码执行完毕后,当前环境就会销毁,全局执行环境直到应用程序退出时才会销毁。
           每个函数都有自己的执行环境,执行流进入一个函数时,函数当前的执行环境被推入环境栈中,此时控制权就由原来的执行环境而移交给当前执行环境下,当函数执行完毕后,栈就会将这个环境推出,控制权也重新回到原来的执行环境下。每一个正在执行的环境都放在环境栈里面。ECMAScript的执行流就是这个机制。
           在javascript最顶层的代码中(不包含任何函数定义内的代码),作用域链是由一个全局对象组成(也可以理解成全局环境下的变量对象),在不包含嵌套的函数体内,作用域链上有两个对象(变量对象),第一个是定义的函数参数和局部变量的对象(变量对象)。如果这个函数体内有嵌套,那么作用域链上至少有三个对象(变量对象)。当定义一个函数的时候,实际上保存了一个作用域链,当代码在一个环境中执行的时候,就会创建一个变量对象的作用域链,换句话说,当函数被执行的时候,刚刚创建的那个存储局部变量的对象(变量对象)就被添加至该函数在定义的时候保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的链,这里我再解释的通俗一点:定义函数的时候保存的那个作用域链只有短短的一截,因为此时函数还没有被调用,所以它不存在什么变量对象,也跟外部没有联系,所以这一截作用域链上面什么都没有,当调用函数的时候,作用域链就会与外界的作用域链相连接,变长,二截或三截或更多,调用的时候内部所有的局部变量都会重新定义一遍,所以就有了变量对象,此时作用域链就会绑上这个变量对象,所以这个作用域链相当于一个全新的作用域链,它更长了。我再结合执行流的机制再补充一点,执行流进入每一个不同的函数的时候,都相当于一次作用域链的拼接,每一个函数内部都有一小截,被调用的时候,就接上外部的执行环境的作用域链,调用完了就断开,换下一个接上,就是这么个过程,所以每次执行函数的时候,作用域链都是新的。这个作用域链的用途是保证对所有能够在当前执行环境下有权访问的变量或函数的有序访问。(换句话说,在当前执行环境下,有哪些变量或函数是可以访问的,我通过作用域链就可以查到,而且是有序的查找,所以我认为其实作用域链就是当前,变量对象的排序)。这个作用域链的前端,是当前执行环境的变量对象。如果这个执行环境是函数,则将其活动对象作为变量对象。活动对象起初只有一个变量,就是arguments对象(全局环境中没有的)。作用域链上,从前端开始往后,下一个变量对象就是当前执行环境外的环境下的变量对象,再下一个就是再外一层环境的变量对象,(这个环境一层层的,就是执行流依次经过的环境,只不过是倒过来,从前往后的,从里到外),一直到最外层的全局执行环境:全局执行环境的变量对象一定是作用域链的最后一个对象。
            标识符的解析就是沿着作用域链从前往后找的。
            作用域链本质上是一个指向变量对象的指针列表,他只引用但不实际包含变量对象。 ——节选自闭包一节。

    这里补充几个延长作用域链的方法(就是把变量对象直接添加到作用域链的前端):

    1,try-catch语句的catch块

    2,with语句(不推荐使用,严格模式下是禁止的):用法如下

    with (document.forms[0]) {
        // 直接访问表单元素
        name.value = "";
        address.value = "";
        email.value = "";
    }  

    这种减少了大量的输入,不用为每个属性添加document.forms[0]前缀,等同于下面

    var f = document.forms[0];
    f.name.value = "";
    f.address.value = "";
    f.email.value = "";

    但是要注意一点,如果with中的变量没有被定义,那么就会创建一个局部或全局变量,with就等于没有,比如:

    with(o) x = 1

    如果o里面有x属性,那么就会直接覆盖为1,如果没有x属性,那么就等同于在当前环境下执行:

    x = 1;

    就会创建一个新的局部或全局变量。

    3,apply和call,这个详见apply、call那篇随笔

    ==========

    2017-12-14,再次理解作用域链:

    函数定义的时候,会创建一条作用域链,当函数调用的时候,会创建一个变量对象来存储它的局部变量,并将这个变量对象添加到定义时候创建的作用域链上,同时创建一条更长的,用来表示函数调用作用域的链条,这个链条不是函数定义时创建的作用域链,那一条是函数自己的,而这一条表示函数调用作用域的链条是对于整个体系而言,它上面有很多不同执行环境下定义的变量对象,每一次执行函数都会在这条大链子前面添加一个变量对象。

  • 相关阅读:
    作业07-Java GUI编程
    作业06-接口、内部类
    作业05-继承、多态、抽象类与接口
    作业14-数据库
    作业13-网络
    作业12-流与文件
    作业11-多线程
    作业10-异常
    作业09-集合与泛型
    作业08-集合
  • 原文地址:https://www.cnblogs.com/yanchenyu/p/7404613.html
Copyright © 2020-2023  润新知