这一章节主要讲解JavaScript中的变量、作用域以及垃圾收集问题。
基本类型和引用类型
JavaScript中的基本类型包括Undefined
、Null
、Boolean
、Number
和String
,而Object
则是引用类型。
基本类型都是按值访问的,访问的都是内存中的实际值。而引用类型的值是保存在内存中的对象,操作引用类型的时候,实际上操作的是指向这块内存的指针而已。
关于引用类型(即对象类型)后面章节会有更详细的介绍。
动态的属性
我们可以为引用类型动态添加属性,也可以改变和删除其属性。但是对于基本类型,虽然在增加属性的时候并不会报错,但是实际在访问这个属性的时候会得到一个undefined
。
var person = new Object();
person.name = "buginux";
alert(person.name); // "buginux:
var name = "buginux";
name.age = 42;
alert(name.age); // undefined
复制变量的值
对于基本类型,从一个变量向另一个变量复制后,两个变量里面的值是完全独立的,改变其中一个并不会影响到另一个。而对于引用类型而言,复制的是指向内存中的对象地址的指针,所以实际上复制后两个变量指向的是同一个对象,改变其中一个变量,就会影响到另一个变量。
var obj1 = new Object();
var obj2 = obj1;
obj1.name = "buginux";
alert(obj2.name); //"buginux"
传递参数
JavaScript中的传参只有按值传递。与变量值的复制类似,在传参的过程中,基本类型就是将值完全复制到参数中,而对于引用类型,传递的是指向内存中对象地址的指针(也是一个值,只不过这个值是一个内存地址)。因此,在函数中操作引用类型,这种变化也会反映在函数的外部。
检测类型
对于基本类型,可以使用typeof
来检测具体的数据类型。但对于引用类型来讲,不管是什么类型的对象,这个typeof
都会返回Object
,实际上我们对于它是什么类型的对象更感兴趣。因此,对于引用类型,使用instanceof
操作符会更加合适。
语法格式:
result = variable instanceof constructor
如果变量是给定引用类型的实例,则instanceof
操作符会返回true
。
alert(person instanceof Object); // 变量 person 是 Object 吗?
alert(colors instanceof Array); // 变量 colors 是 Array 吗?
alert(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗
执行环境及作用域
执行环境是JS中最重要的一个概念,执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个 变量对象,环境中定义的变量和函数都记录在这个对象中。
执行环境分为全局执行环境和函数执行环境。也就是说,每个函数都有一个自己的执行环境。
当代码在一个环境中执行的时,会创建变量对象的一个 作用域链。作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在的环境的变量对象。作用域链的下一个变量对象来自包含环境,直到全局执行环境。
延长作用域链
虽然执行环境只有全局与局部(函数)两种,但是还是有别的办法来延长作用域链:
trh-catch
语句的catch
块with
语句
这两个语句都会在作用域链的前端添加一个变量对象。对with
语句来说,会将指定的对象添加到作用域链中。对catch
语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
没有块级作用域
JavaScript当中没有块级作用域。在其他的类C语言中,由花括号封闭的代码块都有自己的作用域(就是JS中的执行环境),因此可以在其中定义局部变量。而JavaScript当中最小的作用域为函数作用域。
声明变量
使用var
声明的变量会自动被添加到最接近的环境中。而没有使用var
关键字来声明变量时,变量会被添加到全局环境中。
function add(num1, num2) {
var sum = num1 + num2; // 局部变量
return sum;
}
var result = add(10, 20); //30
alert(sum); //由于 sum 不是有效的变量,因此会导致错误
function add(num1, num2) {
sum = num1 + num2; // 全局变量
return sum;
}
var result = add(10, 20); //30
alert(sum); //30
查询标识符
在某个环境中引用一个标识符时,必须通过搜索来确定该标识符代表什么意义。执行过程会从当前的作用域链的前端开始,向上逐级查询。如果在局部环境中搜索到了,则该搜索过程就停止。而如果在局部中没有搜索到,则会继续沿作用域链向上搜索,直到全局环境为止。如果全局环境中也没有找到,说明该变量未声明。
var color = "blue"; // 全局环境搜索到标识符
function getColor(){
return color;
}
alert(getColor()); //"blue"
var color = "blue";
function getColor(){
var color = "red"; // 局部环境搜索到标识符,停止
return color;
}
alert(getColor()); //"red"
垃圾收集
JavaScript也具有自动垃圾收集机制,即执行环境会负责管理代码执行过程中使用的内存。
对于垃圾回收机制通常有两种策略:标记清除与引用计数。
标记清除
标记清除是JavaScript中最常用的垃圾收集机制。当一个变量进入环境时,就被标记为“进入环境”,而当变量离开时,变被标记为“离开环境”。垃圾收集器定周期检查所有变量的标记,将标记为“离开环境”的变量内存回收。
引用计数
引用计数跟踪记录了每一个变量被引用的次数,将引用这个变量的计数为0时,它便可以被回收。但是这个机制存在一个问题,那就是循环引用,两个变量之间可能存在对对方的引用,这样,虽然这两个变量都没有被使用了,但是由于他们的引用计数不为0,则他们永远不能被回收。当前的JavaScript引擎都不再使用这一方法了。
性能问题
如果无节制地分配大量的内存,则在垃圾回收的时候会地性能造成影响,因此应该对内存的使用多加注意。
管理内存
因为JavaScript是运行在浏览器上的,而Web浏览器通常被分配的内存是有限的。因此为了确保JavaScript的运行效率和让页面获得更好的性能,必须确保占用最少的内存。
优化内存占用的最佳方式就是为执行中的代码保存必要的数据。一旦数据不再有用,最好将其设置为null
,这称为解除引用。为一个变量解除引用并不代表立即回收该变量的内存,解除引用的真正作用是使变量脱离执行环境,在下次进行垃圾回收时将其回收。
小结
本章内容主要是一些概念性的内容,比较枯燥。但是这些概念十分重要,有助于在其后的学习当中更好地理解JavaScript的代码,及其运行过程,因此有必要好好学习并在有必要的时候回头进行参考。