一、基本类型和引用类型的值
ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。
基本类型值:简单的数据段(分别为:Undefined、Null、Boolean、Number、String)。
引用类型值:是保存在内存中的对象。
1.1、动态的属性
对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法
// 只能给引用类型的值动态地添加属性
var person = new Object(); // 创建对象
person.name = "Nicholas";
alert(person.name);
// 对于基本类型值不能添加属性
var name = "Nicholas";
name.age = 27;
alert(name.age); // 显示 undefined
1.2、复制变量值
(1)如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。
(2)变量之间复制引用类型的值时,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。
var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'Nicholas';
console.log(obj2.name); // 'Nicholas'
关系结构如图1所示。
图1
1.3、传递参数
(1)ECMAScript中所有函数的参数都是按值传递的。
(2)基本类型值的传递如何基本类型变量的复制一样,引用类型值的传递,则如同引用类型变量的复制一样。
(3)在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。
function setName(obj) {
obj.name = 'Nicholas';
}
var person = new Object();
setName(person);
console.log(person.name); // 'Nicholas'
注意:把对象传递给函数参数实际也是按值传递的,具体例子如下所示:
function setName(obj)
{
obj.name = "Nicholas";
obj = new Object(); // 这只是局部对象,
obj.name = "Greg"; // 这个局部对象会在函数执行完毕后立即销毁。
}
var person = new Object();
setName(person);
alert(person.name); // 显示 Nicholas
1.4、检测类型
typeof 操作符检测变量类型,结果为:string(字符型)、number(数值型)、boolean(布尔型)、undefined、object(Null)、object(Object)。检测基本类型值的类型。
instanceof 用来检测对象属于什么类型的对象。
- 根据规定,所有引用类型的值都是Object的实例
- 在检测一个引用类型值和Object构造函数时,instanceof 操作符始终会返回true;
- 在检测一个基本类型值时,instanceof 操作符始终会返回false,因为基本类型不是对象。
// instanceof 检测引用类型值的类型 格式
// result = variable instanceof constructor
var person = new Object();
var name = "Nicholas";
alert(person instanceof Object); // true
alert(name instanceof Object); // false,当检测类型不是对象时返回 false
二、执行环境及作用域(实际上讲的是:全局变量和局部变量)
(1)执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。
(2)每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
(3)在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的。全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁。
(4)每个函数都有自己的执行环境。
(5)当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。
(6)全局执行环境的变量对象始终都是作用域链中的最后一个对象。
(7)每个环境都可以向上搜索作用域链,但是不能向下搜索作用域链。
2.1、延长作用域链
虽然执行环境的类型总共有两种——全局和局部(函数),但还是有其他办法来延长作用域链。这么说是因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。
作用域链会延长的两种情况:
(1)try-catch语句的catch块(会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。)
(2)with语句。(会将指定的对象添加到作用域链中。)
function buildUrl() {
var qs = '?debug=true';
with(location) {
var url = href + qs;
}
return url;
}
alert(buildUrl());
2.2、没有块级作用域
在ES5中,JS是没有块级作用域。
if (true)
{
var color = "blue";
}
alert(color); // 变量 color 并没有被销毁
(1)声明变量
使用var声明的变量会自动被添加到最接近的环境中。在函数内部,最接近的环境就是函数的局部环境;在with语句中,最接近的环境就是函数环境。如果初始化变量时没有使用var声明,该变量会自动被添加到全局环境。
function add(num1, num2)
{
var sum = num1 + num2; //此时sum为局部变量,如果去掉var则为全局变量
return sum;
}
var result = add(10, 20); // 30
alert(sum); //因为是句变量所以在函数的外部不能被访问
(2)查询标识符
- 标识符的查找过程:从作用域链的最前端开始搜索要查询的标识符,然后逐级往上进行搜索,直到搜索到要查询的标识符或追溯到全局环境中还没有搜索到要查找的标识符,则意味着该变量尚未声明。
- 访问局部变量要比访问全局变量更快,因为不用向上搜索作用域链。
三、垃圾收集
- JS编程时,内存的分配及回收是自动进行管理的。
- 离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。
- “标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然后再回收其内存。
- 另一种垃圾收集算法是“引用计数”,这种算法的思想是跟踪记录所有值被引用的次数。JS引擎目前都不再使用这种算法;但在 IE 中访问非原生 JS 对象(如 DOM 元素)时,这种算法任然可能会导致问题。
- 当代码中存在循环引用现象时,“引用计数”算法就会导致问题。
- 解除变量的引用不仅有助于消除循环引用现象,而且对垃圾收集也有好处。为了确保有效地回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。
参考文献
[1] 《JavaScript高级程序设计(第3版)》