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

基本类型和引用类型的值

  • 只能给引用类型值动态地添加属性
  • 给基本类型添加属性不会报错,但添加后也访问不到

传递参数

  • 访问变量有按值和按引用两种方式,而参数只能按值传递(复制值或指针)
  • 在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量,就是 arguments 对象中的一个元素
  • 在向参数传递引用类型的值时,会把 这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部
  • 基本类型作为参数传递进去,是复制一份值,所以对这个参数的操作不会改变外部给这个参数传值的变量的值
  • 引用类型作为参数传递进去,是复制一份地址,参数所指向的地址和外部变量所指向的地址相同,所以对参数的操作会影响外部变量。
function setName(obj) {
    obj.name = "Nicholas";  //obj和person指向同一地址,改obj等于改person
    obj = new Object(); //obj指向的地址变了
    obj.name = "Greg"; //新obj的地址和person不同,改obj不影响person
}

var person = new Object();
setName(person); //把person的地址复制一份给obj
console.log(person.name);    //"Nicholas"

执行环境及作用域

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。 而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript 程序中的执行流 正是由这个方便的机制控制着。

作用域链

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

  • 标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始, 然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。
var color = "blue";

function changeColor() {
    var anotherColor = "red";

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;    // 这里可以访问 color、anotherColor、 tempColor    
    }
    // 这里可以访问 color 和 anotherColor,但不能访问 tempColor            
    swapColors();
}

// 这里只能访问 color 
changeColor();

作用域链

图中矩形表示特定的执行环境。其中,内部环境可以通过作用域链访问所有的外部环境,但 外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每个环境都 可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个 执行环境。

延长作用域链

  • 可以通过with或者try…catch在作用域链的前端添加一个变量对象,如果在当前作用域找不到对应变量时,可以在前端的这个变量对象中找

查询标识符

  • 如果初始化变量时没有使用 var 声明,该变量会自 动被添加到全局环境。
  • 使用一个变量时,查找其对应值的过程:

当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到 了该标识符,搜索过程停止,变量就绪。如果在局部环境中没有找到该变量名,则继续沿作用域链向上搜索。搜索过程将一直追溯到全局环境的变量对象。如果在全局环境中也没有找到这个标识符,则意味 着该变量尚未声明

  • 搜索过程中一旦找到同名变量,就会立即停止搜索,如果局部环境中存在着同名标识符,就不会使用位于父环境中的标识符

垃圾收集

  • JavaScript 具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。
  • 垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间), 周期性地找出那些不再继续使用的变量,然后释放其占用的内存
  • 函数执行完之后就会将函数内的局部变量清除,释放内存
  • 老版本IE启动垃圾回收是按照变量数,对象数等固定值来启动的,到达临界值就会启动垃圾回收,但在变量较多的js文件中,就会一直调用垃圾回收,影响性能
  • 新版本浏览器一般都是动态启动垃圾回收,如超过内存的某个百分比

标记清除

JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。

当变量进入环境(例如,在函 数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。

而当变量离开环境时,则将其 标记为“离开环境”。 可以使用任何方式来标记变量。比如,可以通过翻转某个特殊的位来记录一个变量何时进入环境, 或者使用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪哪个变量发生了变化。说 到底,如何标记变量其实并不重要,关键在于采取什么策略。

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方 式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记 的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器 完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

引用计数

另一种不太常见的垃圾收集策略叫做引用计数(reference counting)。引用计数的含义是跟踪记录每 个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1。 如果同一个值又被赋给另一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取 得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0时,则说明没有办法再访问这 个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存。

  • 循环引用指的是对象 A中包含一个指向对象 B的指针,而对象 B中也包含一个指向对象 A的 引用
function problem() {
    var objectA = new Object();
    var objectB = new Object();

    objectA.someOtherObject = objectB; 
    objectB.anotherObject = objectA;
    // objectA 和 objectB计数都为2,且无法清零
} 
  • 解决方法,手动清除:
    将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就 会删除这些值并回收它们占用的内存。

管理内存

  • 优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——这个 做法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在 它们离开执行环境时自动被解除引用
  • 解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离 执行环境,以便垃圾收集器下次运行时将其回收。

小结

  • 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
  • 引用类型的值是对象,保存在堆内存中;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值