美文网首页
第四章——变量、作用域、内存问题

第四章——变量、作用域、内存问题

作者: 26001a36aa12 | 来源:发表于2016-05-27 21:58 被阅读29次

    基本类型和引用类型的值


    javascript引用数据类型是保存在堆内存中的对象,JavaScript不允许直接访问堆内存空间中的位置和操作堆内存空间,只能通过操作对象在栈内存中的引用地址。所以引用类型的数据,在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存在堆内存中的对象。

    下面我们来演示这个引用数据类型赋值过程:


    自然,给obj2添加name属性,实际上是给堆内存中的对象添加了name属性,obj2和obj1在栈内存中保存的只是堆内存对象的引用地址,虽然也是拷贝了一份,但指向的对象却是同一个。故而改变obj2引起了obj1的改变。

    基本类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值则是指那些保存在堆内存中的对象,即变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。简而言之,堆内存存放引用值,栈内存存放固定类型值。

    1.动态的属性

    var person = new Object();
    person.name = "hello";
    

    2.复制变量值

    基本类型值的复制互不影响,引用对象的复制则会指向同一个对象。

    3.传递参数

    ECMAScript中所有函数的参数都是按照值传递的,也就是说,把函数外面的值复制给函数内部的参数,和正常的复制没有差别。

    局部对象会在函数执行完毕后立即销毁。

    4.检测类型

    typeOf操作符在对象上只能返回Object类型,但不能返回具体类型,因此,我们常用instanceof来检测。

    result = variable instanceof constructor  //variable是不是constructor“对象”的实例
    

    执行环境及作用域


    执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。分为全局执行环境和函数执行环境。

    每个执行环境都有一个与之关联的“变量对象”。环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

    全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。(例如,在Web浏览器中,全局执行环境被认为是windows对象,因此所有全局变量和函数都是作为windows对象的属性和方法创建的。)某个执行环境中的所有代码执行完之后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭浏览器或网页——时才会被销毁)。

    每个函数都有自己的“执行环境”。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。

    当代码在一个环境中执行时,会创建变量对象的一个“作用域链”。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含环境,以此类推。一直延伸到全局执行环境;全局执行环境的变量对象始终是作用域链的最后一个对象。

    标识符的解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链前端开始,逐级回溯,直到找到标识符。

    1.延长作用域链

    虽然执行环境的类型只有两种——全局和局部,但还有其他办法来延长作用域链。

    这么说是因为,有些语句可以在作用域链前端临时增加一个变量对象,该变量对象会在代码执行过后被移除。

    • try-catch语句的catch块:创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
    • with语句:将指定对象添加到作用域链中。

    2.没有块级作用域

    声明变量
    使用var声明的变量会被自动添加到最接近的环境中。如果初始化变量时未使用var,则会添加到全局环境中。

    查询标识符
    如果局部环境中存在着同名标识符,就不会使用位于父环境中的标识符。

    垃圾收集


    JavaScript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。

    原理很简单:找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性的执行这一操作。

    垃圾收集器必须跟踪哪个变量有用,哪个变量没用,对于不再有用的变量打上标记,以备将来收回其占用的内存。其中,用于标记无用变量的策略可能会因实现而异,但具体到浏览器中的实现,则通常有两个策略。

    1.标记清除

    当变量进入环境(例如在函数中声明一个变量),就将这个变量标记为“进入环境”。不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就会用到它们。而当变量离开环境,则将其标记为“离开环境”。

    可以使用任何方式标记变量。例如:(1)可以通过翻转某个特殊的位来记录一个变量何时进入环境;(2)使用一个“进入环境”变量列表和“离开环境”变量列表跟踪哪个量发生变化。

    首先,垃圾收集器会给存储在内存中的所有变量加上标记。然后,它会去掉环境中的变量以及被环境中变量引用的变量的标记。而在此之后,再被加上标记的变量(可能是加上另一种标记)将被视为准备删除的变量。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收空间。

    2.引用计数

    跟踪记录每个值被引用的次数。

    当声明了一个变量,并将一个引用类型赋给该变量时,这个值引用次数就是1,再赋给另一个的话再加1。相反,如果包含对这个值引用的变量取了另外一个值,则这个值引用次数减1,直到为0,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的内存。

    严重问题:循环引用中引用次数永远不会是0,如果包含循环引用的函数重复多次调用,就会导致大量内存得不到回收。

    为了避免循环引用问题,最好是在不使用他们的时候手工断开元素之间的连接。(将其设为null)

    3.性能问题

    垃圾回收器是周期性运行的,而且如果为变量分配的内存数量很可观,那么回收工作量也是相当大的。因此,确定时间间隔是非常重要的问题。

    4.管理内存

    一般来说,分配给Web浏览器的可用内存比桌面程序少,因为防止运行JavaScript的网页耗尽全部系统内存导致崩溃。

    内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量。因此,确保占用最少的内存可以让页面获得更好的性能。

    优化内存占用的最佳方式就是为执行中的代码只保存必要的数据。一旦数据不再有用,则设置为null来“解除引用”。这一做法适用于大多数全局变量和全局对象属性。

    解除引用的真正作用是让值脱离执行环境,以便垃圾回收器下次运行时将其回收。并不意味着立即回收。

    相关文章

      网友评论

          本文标题:第四章——变量、作用域、内存问题

          本文链接:https://www.haomeiwen.com/subject/sexfdttx.html