JS红皮书读书笔记-04-变量、作用域和内存问题

提醒:本文发布于 1489 天前,文章内容可能 因技术时效性过期 或 被重新修改,请谨慎参考。

TOC
  1. 1. 基本类型和引用类型的值
    1. 1.1. 动态的属性
    2. 1.2. 复制变量值
    3. 1.3. 参数传递
    4. 1.4. 检测类型
  2. 2. 执行环境及作用域
    1. 2.1. 什么是执行环境
    2. 2.2. 执行环境种类
    3. 2.3. 执行环境的机制
    4. 2.4. 什么是作用域链
    5. 2.5. 延长作用域链
    6. 2.6. 没有块级作用域(ES5)
  3. 3. 垃圾收集
    1. 3.1. 标记清除
    2. 3.2. 引用计数
    3. 3.3. 性能问题&管理内存

基本类型和引用类型的值

再次复习一下, ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。

动态的属性

知识点:

  • 对基本类型添加属性虽然不会报错, 但是没有效果
  • 操作对象时, 操作的是对象的引用而不是实际的对象

复制变量值

知识点:

  • 基本类型变量复制是完全复制
    基本类型变量复制
    基本类型变量复制原理
  • 引用类型变量复制是复制引用
    引用类型变量复制原理
    引用类型变量复制原理

参数传递

JS函数参数传递是按值传递, 如果参数的值是基本类型, 这个很好理解.

function addTen(num) { 
num+=10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20,没有变化
alert(result); //30

但是参数是引用类型, 可能就不是很好理解, 我们可以看两个示例

示例1:

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

这个例子看来很像引用传递, 但是传参那一瞬间: obj和person的值都是同一个.即使它是按值传递, 也会因为引用类型必须按照引用的方式访问, 反映到person.name上, 所以视觉上看起来外部变量被修改了.

我们再来看示例2:

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

如果是按引用传递, 那么person.name的值肯定户变成”Greg”

检测类型

  • 基本类型用 typeof关键字检测
  • 引用类型用instanceof 检测

执行环境及作用域

什么是执行环境

执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象( variable object ),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

执行环境种类

执行环境有两种, 一个是全局, 一个是局部(函数)

在Web浏览器中,全局执行环境被认为是window对象。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出: 例如关闭网页或浏览器时才会被销毁)

执行环境的机制

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

什么是作用域链

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain )。它的用途是: 保证(对执行环境有权访问的)所有变量和函数的有序访问

作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象(即arguments对象)。

作用域链中的下一个变量对象来自外部环境(有可能还是函数),而再下一个变量对象同样来自下一个外部环境,一直延续到全局执行环境(最后一个变量对象)。

JS的变量, 就根据作用域链来查找, 如果一直到全局环境都找不到, 那就会报错.

为了方便理解, 我们来看一个例子:

var color = "blue";

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

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

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

作用域链

本示例的作用域链

延长作用域链

两种方式延长作用域链

  • try-catch
  • with (避免使用这个语句)

没有块级作用域(ES5)

es5没有块级作用域, 所以你在if判断语句之后, 或者for循环之后 都能访问到其中代码块的变量.

变量声明: 变量使用var关键字声明之后, 自动添加到离它最近的环境, 如果没有使用var声明变量, 这个变量则会被添加到全局环境.

变量搜索: 搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到了该标识符,搜索过程停止,变量就绪, 如果到全局还没有搜索到, 说明变量没有声明, 将会报错。从这里可以看出, 搜索变量是有代价的, 局部变量会更快, 因此尽可能不要声明那么多全局环境.

垃圾收集

标记清除

引用计数

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1。如果同一个值又被赋给另一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。

但是这会产生一个问题: 循环引用

function problem(){
var a = {name:'a'},
b = {name:'b'};
a.bro = b;
b.bro = a;
}
problem(); // 函数执行完毕, 变量依然被循环引用 无法销毁

性能问题&管理内存

如果能手动释放的内存, 尽可能手动释放. 一旦数据不再有用,最好通过将其值设置为 null 来释放其引用 这个做法叫做解除引用(dereferencing)。

不过,解除一个值的引用并不意味若自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

访客评论