javascript
读书笔记
变量、作用域和内存问题
1.x 变量类型
var a = 1;
var b = a; // 创建一个新的变量,和新的值
////////////////////
var p = new Object();
var q = p; // 创建一个新的变量,指向 p 指向的内存。
为什么说对于引用类型,参数传递是按值传递。
var person = new Object();
function setname(obj) {
obj.name = '张飞';
obj = new Object();
obj.name = '王菲';
}
setname(person);
console.log(person.name); // 张飞
输出结果是
张飞
。
(这一点上,和java
的表现形式相同)
class Person {
public String name;
}
public class Main {
public static void main(String[] args) {
Person p = new Person();
setName(p);
System.out.println(p.name); // 张飞
}
public static void setName(Person person) {
person.name = "张飞";
person = new Person();
person.name = "王菲";
}
}
输出
张飞
-<>-
其实这个问题,涉及到两个点:参数传递与变量作用域。
2.x 执行环境
var color = 'blue';
function changeColor() {
var anotherColor = "red";
function swapColor() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问color,anotherColor,tempColor
}
swapColor();
// 这里可以访问 color,anotherColor,但不能访问 tempColor
}
// 这里能访问 color,但不能访问 anotherColor 和 tempColor.
console.log(color);
内部环境可以通过作用域链(
scope chain
)访问所有外部环境,但是外部环境不能访问内部环境中的任何变量和函数。这些环境直接的联系是线性的,有序的。每个环境都可以向上搜索作用域链,以查询变量和函数名;但是任何环境都不能通关过向下搜索作用域链而进入另一个执行环境。
对于这里的swapColor()
而言,其作用域链中包含3个对象:swapColor()
的变量对象、changeColor()
的变量对象和全局变量对象。swapColor()
的局部环境开始时会先在自己的变量对象中搜索变量和函数名,如果搜索不到则再搜索上一级的作用域链。changeColor()
的作用域链中只包含两个对象:它自己的变量对象和全局的变量对象。这也就是说,它不能访问swapColor()
的环境。
2.1.x 延长作用域链
虽然说执行环境的类型总共只有两种–全局和局部(函数),但是还是有其他办法来延长作用域链。这么说是因为有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会再代码执行后被移除。
在两种情况下会发生这种现象。具体来说,就是当执行流进入下列任何一个语句时,作用域链就会得到延长:
try-catch
语句的catch
块;with
语句。
这两个语句都会在作用域链的前端增加一个变量对象。对于with
语句来说,会将指定的的对象添加到作用域链中,对于catch
语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
function buildUrl(){
var qs="?debug=true";
with(location){
url = href + qs;
}
return url;
}
在此,with
语句接收的是location
对象,因此其变量对象中就包含了location
对象的所有属性和方法,而这个变量对象被添加到了作用域链的前端。buildUrl()
函数中定义了一个变量qs
。当在with
语句中引用变量href
时(实际引用的是location.href
),可以在当前执行环境的变量对象中找到。当引用变量qs
时,引用的则是在buildUrl()
中定义的那个变量,而该变量位于函数环境的变量对象中。至于with
语句内容,则定义了一个名为url
的变量,因而url
就成了函数执行环境的一部分,所有可以作为函数的值被返回。
2.2.x 没有块级作用域
Javascript
没有块级作用域经常会导致理解上的困惑。在其他类C
的语言中,由花括号的代码块都有自己的中作用域(如果用ECMAScript
的话来讲,就是它们自己的执行环境),因而支持根据条件来定义变量。例如,下面的代码在Javascript
中并不好得到想象中的结果:
if (true){
var color="blue";
}
alert(color); // "blue"
这里是在一个if
语句中定义了变量color
。如果是在C / C++ / Java
中color
会在if
语句执行完毕后销毁。但在Javascript
中,if
语句中的变量声明会将变量添加到当前的执行环境(在这里是全局环境)中。
- 声明变量
使用var
声明的变量会自动被添加到最近的环境中。在函数内容,最近的环境就是函数的局部环境;在with
语句中,最接近的环境是函数环境。如果初始化变量时没有使用var
声明,该变量会自动被添加到全局环境。(严苛模式下,不使用var
声明而直接使用,会出现语法错误。)
2.3.x 管理内存
确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为null
来释放其引用–这个做法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在它们离开执行环境时自动被解除引用,如下面这个例子所示:
function createPerson(name){
var person = new Object();
person.name = name;
return person;
}
var globalPerson = cretePerson('张飞');
// 不再使用该变量时,手动解除 globalPerson 的引用
globalPerson = null;
在这个例子中,变量 globalPerson
取得了createPerson()
函数的返回值。在createPerson()
函数内部,我们创建了一个对象并将其赋给局部变量person
,然后又为该变量添加了一个名为name
的属性。最后,当调用这个函数时,person
以函数值的形式返回被赋给全局变量globalPerson
。由于person
在createPerson()
函数执行完毕后就离开了其执行环境,因此无需我们显示地去为它解除引用。但是对于全局变量globalPerson
而言,则需要我们在不使用它的时候手工为它解除引用,这也正是上面例子中最后一行代码的目的。
2.4.x 小结
Javascript
变量可以用来保存两种类型的值:基本类型值和引用类型值。基本类型的值源自5
种基本数据类型:Undefined,Null,Boolean,Number,String
。基本类型值和引用类型值具有以下特点:
- 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
- 从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;
- 引用类型的值是对象,保存在堆内存中;
- 包含引用类型的值实际上包含的并不是对象本身,而是一个指向该对象的指针;
- 从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象;
- 确认一个值是哪种基本类型可以使用
typeof
操作符,而确定一个值是哪种引用类型可以使用instanceof
操作符。
所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。以下是关于执行环境的几点总结:
- 执行环境有全局执行环境(也称为全局环境)和函数执行环境之分;
- 每次进入一个新的执行环境,都会创建一个用于搜索变量和函数的作用域链;
- 函数的局部环境不仅有权访问函数的作用域变量,而且有权访问其包含(父)环境,乃至全局环境;
- 全局环境只能访问全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
- 变量的执行环境有助于确定应该何时释放内存。
Javascript
的垃圾收集例程总结:
- 离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。
- “标记清除”是目前主流的垃圾收集算法,这种算法是给当前不使用的值加上标记,然后再回收其内存。
- 另一种垃圾收集算法是“引用计数”,这种算法的思想是跟踪记录所有值被引用的次数。
Javascript
引擎目前都不再使用这种算法;但是在IE
中访问非原生Javascript
对象(如DOM
元素,BOM
元素)时,这种算法仍然可能会导致问题。 - 当代码中存在循环引用的现象时,“引用计数”算法就会导致问题。
- 解除变量的引用不仅有助于消除循环引用现象,而且对垃圾回收也有好处,为了确保有效地回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。