第四章 变量、作用域和内存问题
4.1 基本类型和引用类型的值
这是ECMA包含的两种不同数据类型。基本类型是保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置;
引用类型则是指那些保存在堆内存中的对象,意思是变量中保存的实际上是一个指针,这个指针指向内存中的另一个保存对象的位置。
在将一个值赋给变量时,必须确定这个值是基本类型值还是引用类型值。ch3提到的五种基本数据类型:Undefined,Null,Boolean,Number,String.这五种基本数据类型的值在内存中分别占有固定大小的空间,所以可以把它们的值保存在栈内存中,对于保存基本类型的值,我们说它们是按值访问的。
如果赋给变量的是一个引用类型的值,则必须在堆内存中为这个值分配空间,由于这种值的大小不固定,因此不能把它们保存在栈内存中。但内存地址的大小是固定的,因此可以把内存地址保存在栈内存中。先从栈中读取内存地址,再找到保存在堆内存中的值。保存在堆内存中的数据不是按照顺序访问的,因为每个对象所需要的空间并不相等。
(栈内存、堆内存、内存空间?)
似乎就是说栈内存中要么保存着基本数据类型的值要么保存的是引用类型在堆内存中的地址。
4.1.1 动态属性
定义基本类型值和引用类型值的方式是类似的:创建一个变量并为该变量赋值。但是对不同类型值可以执行的操作大相径庭。对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。
var person=new Object();//引用类型就是对象吗?
person.name="Nicolas";
alert(person.name);
不能给基本类型的值添加属性,尽管不会报错,但如若输出,是“Undefined”
4.1.2 复制变量值
基本类型:从一个变量向另一个变量复制基本类型的值,会在栈中创建一个新值。
引用类型:同样也会将存储在栈中的值(也只是地址相当于指针了)复制一份放到为新变量分配的空间中。指向同一个对象。
4.1.3 传递参数
ECMA中所有函数的参数都是按值传递。
基本类型的值传递给参数,其在函数内的操作不会影响它本身的值。而引用类型就会了。说起来略显抽象,其实也就是C那一套。妈的反正就是都传递内存中存的东西呗,一个是值而另一个是指针。这书讲这儿讲得一点不清楚,没基础的肯定都晕了。
看下面一个例子
function setName(obj){
obj.name="Nicolas";
obj=new Object();
obj.name="Greg";
}
var person=new Object();
setName(person);//刚刚写成person.setName() 注意一下
alert(person.name);//Nicolas 因为是按值传递
4.1.4 检测类型
typeOf操作符是确定一个变量是字符串、数值、布尔值还是Undefined的最佳工具。如果变量的值是一个对象或null,则typeOf会返回“object”。使用typeOf检测函数时,会返回function。
根据规定,所有引用类型的值都是Object的实例。因此,在检测一个引用类型值和Object构造函数时,instanceof操作符始终返回true。
4.2 执行环境和作用域
执行环境是js中最重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但是解析器在处理数据时会在后台使用它。
每个函数在被调用时都会创建自己的调用环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
当代码在一个环境中执行时,会创建由变量对象构成的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端始终是当前执行的代码所在环境的变量对象。(真抽象,不知道还要看多少概念)
标识符解析是沿着作用域链一级一级地搜索标识符的过程。看下面这个例子
var color="blue";
function changeColor()
{
if(color=="blue"){
color="red";
}
else{
color="yellow";
}
}
changeColor();
//changeColor("red")或是changeColor(color1)都没有用
alert("color is now "+color);//red
//changeColor的作用域包含两个对象:他自己的变量对象(其中定义着arguments对象)和全局环境的变量对象)
此外,在局部作用域中定义的变量可以在局部环境中与全局变量互换使用,如下面例子
var color="blue";
function changeColor()
{
var anotherColor="red";
function swapColors(){
var tempColor=anotherColor;
anotherColor=color;//blue
color=tempColor;//red
//这里可以访问color, anotherColor和tempColor
}
//这里可以访问color和anotherColor,但不能访问tempColor
swapColors();
}
changeColor();
//这里不能访问anotherColor和tempColor,但可以访问color
alert("color is now "+color);//red
内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。
注:函数参数也被当做变量来对待,因此其访问规则与执行环境中的其它变量相同。
4.2.1 延长作用域链
延长办法:当执行流进入下列任何一个语句时,作用域链就会得到加长。
......try-catch的catch块
......with语句
这两个语句都会在链的前端添加一个变量对象,对with语句来说,其变量对象中包含着为指定对象的所有属性和方法所做的变量声明;对catch语句来说,其变量对象中包含的是被抛出的错误对象的声明。这些变量对象都是只读的,因此在with和catch语句中声明的变量都会被添加到所在执行环境的变量对象中。下面看一个例子
function buildUrl(){
var qs="?debug=true";
with(location){
var url=href+qs;//实际上是location.href
}//出了with框仍然有效
return url;
}
var result=buildUrl();
alert(result);
4.2.2 没有块级作用域
if(true){
var color="blue";
}
alert(color);//blue
在C/C++/JAVA中,color会在if语句执行完毕后被销毁。但在JavaScript中,if语句中的变量声明会将变量添加到当前的执行环境;由for语句创建的变量i即使在for循环结束后,也依旧会存在于循环外部的执行环境中。
js没有块级作用域,函数没有重载。
1.声明变量
在使用var声明变量时,这个变量将被自动添加到距离最近的可用环境中。对于函数而言,这个最近的环境指函数的局部环境;对于前面例子的with语句而言,这个最近的环境也是函数的环境。如果变量在未经声明的情况下被初始化,那么变量会被自动添加到全局环境。但绝对不推荐这么写。
2.查询标识符
当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索作用域链来查询与给定名字匹配的标识符。将一直追溯到全局环境的变量对象。
变量查询也是有代价的。
4.3 垃圾收集
具有自动垃圾收集机制。
4.3.1 标记清除
4.3.2 引用计数
4.3.3 性能问题
4.3.4 管理内存