3. 变量、内存、作用域
3.1 基本类型和引用类型的值
ECMAScript 变量可能包含两种不同数据类型的值:基本类型值【Undefined、Null、Boolean、Number 和 String。 这 5 种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值】和引用类型值【在操作对象时,实际上是在操作对象的引用而不是实际的对象。为此,引用类型的值是按引用访问的】。基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。
-
动态的属性:
对于引用类型的值,可以添加、更改、删除属性和方法;但是基本类型不可以这么做。
-
复制变量值:
当复制一个基本类型的值时,会创建一个新的变量副本。两个变量完全独立。
当复制一个引用类型的值时,复制的是一个引用。两个变量都引用一个对象。当改变原变量的值时,新复制的也会改变。
var a=[1,2,3]; var b=a; a[0]=0; alert(b); //0,2,3
-
传递参数:
当传递一个基本类型的值时,是按值传递,不能更改外部的值。
当传递一个引用类型的值时,是引用传递,可以更改外部的值。
-
检测类型:
typeof xxx
变量 instanceof 类型 /pattern/i instanceof RegExp => true
3.2 执行环境和作用域
执行环境(execution context) 是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。
每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。虽然无法通过代码访问这个对象,但是解析器在处理数据时会在后台使用它。
全局执行环境是最外围的一个执行环境。根据 ECMAScript 实现所在的宿主环境不同(浏览器、Node.js),表示执行环境的对象也不一样。在 Web 浏览器中,全局执行环境被认为是 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。
每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。ECMAScript 程序中的执行流正是由这个方便的机制控制着。
当代码在一个环境中执行时,会创建变量对象的一个作用域链(sope chain)。作用域链的用途是保证对有权访问的执行环境里的变量和函数的有序访问。作用域链的前端,是当前所执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象最开始只包含一个变量,即arguments对象(不再全局环境中存在)。作用域中的下级各变量对象来自包含(外层)环境,再下一个变量来自下一个包含环境。这样一直延伸到全局执行环境,全局执行换金过得变量对象始终都是作用域链的最后一个对象。
标识符的解析是沿着作用域链一级一级地搜索标识符的过程,搜索过程从作用域链的前端开始,然后逐级地向后回溯,知道找到标识符为止(找不到则发生错误)。
-
例如:
/// 下面这段代码中,changeColor()的作用域链中包含两个对象:他自己变量对象(其中定义着arguments对象) 和全局环境的变量对象。在函数内部可以访问变量color就是因为可以再这个作用域链中找到它。
var color = "blue";
function changeColor(){
if (color === "blue"){
color = "red";
} else {
color = "blue";
}
}
changeColor();
alert("Color is now " + color); //red
///以下代码共涉及 3 个执行环境:全局环境、changeColor()的局部环境和 swapColors()的局部环境。
// 2. 全局环境中有一个变量 color 和一个函数 changeColor()。
// 1. changeColor()的局部环境中有一个名为 anotherColor 的变量和一个名为 swapColors()的函数,但它也可以访问全局环境中的变量 color。
// 0. swapColors()的局部环境中有一个变量 tempColor,该变量只能在这个环境中访问到。无论全局环境还是 changeColor()的局部环境都无权访问 tempColor。然而,在 swapColors()内部则可以访问其他两个环境中的所有变量,因为那两个环境是它的父执行环境。
var color = "blue";
function changeColor(){
var anotherColor = "red";
function swapColors(){
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问 color、anotherColor 和 tempColor
}
// 这里可以访问 color 和 anotherColor,但不能访问 tempColor
swapColors();
}
// 这里只能访问 color
changeColor(); 内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。
-
函数参数也被当作变量来对待,因此其访问规则与执行环境中的其他变量相同。
-
延长作用域链:
//虽然执行环境的类型总共只有两种——全局和局部(函数),但还是有其他办法来延长作用域链。就是当执行流进入下列任何一个语句时,作用域链就会得到加长:
// try-catch 语句的 catch 块;
// with 语句。
//这两个语句都会在作用域链的前端添加一个变量对象。对 with 语句来说,会将指定的对象添加到作用域链中。
对 catch 语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。下面看一个例子。
function buildUrl() {
var qs = "?debug=true";
with(location){
var url = href + qs;
}
return url;
}
//with 语句接收的是 location 对象,因此其变量对象中就包含了 location 对象的所有属性和方法,而这个变量对象被添加到了作用域链的前端。
//buildUrl()函数中定义了一个变量 qs。当在with 语句中引用变量 href 时(实际引用的是 location.href),可以在当前执行环境的变量对象中找到。
当引用变量 qs 时,引用的则是在 buildUrl()中定义的那个变量,而该变量位于函数环境的变量对象中。至于 with 语句内部,则定义了一个名为 url 的变量,
因而 url 就成了函数执行环境的一部分,所以可以作为函数的值被返回。
-
-
没有块级作用域:
// 在if语句中定义了变量color,如果在C/C++、Java中,color会在if语句执行完毕后被销毁。但在JavaScript中,if语句中的变量声明会将变量加到当前执行环境中(这里是全局环境)。
if (true) {
var color = "blue";
}
alert(color); // "blue"
// 对于有块级作用域的语言来说,for 语句初始化变量的表达式所定义的变量,只会存在于循环的环境之中。而对于 JavaScript 来说,由 for 语句创建的变量 i 即使在 for 循环执行结束后,也依旧会存在于循环外部的执行环境中。
for(var i = 0; i < 10; i++){
doSomething();
}
alert(i); //
// 【变量声明】
// 如果使用 var 声明变量,则会被自动添加到最近的环境中。在函数内部,最接近的环境就是函数的局部环境。如果初始化变量时,没有使用var声明,则该变量会被自添加到全局环境中。
// 【查询标识符】
//当在某个环境中引用一个标识符时,必须通过搜索来确定该标识符实际代表什么。搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到了该标识符,搜索过程停止,变量就绪。如果在局部环境中没有找到该变量名,则继续沿作用域链向上搜索。搜索过程将一直追溯到全局环境的变量对象。如果在全局环境中也没有找到这个标识符,则意味着该变量尚未声明