4.1.3 传递参数
ECMAScript 中所有函数的参数都是按值传递的。也就是说,
把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。
基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。
有不少开发人员在这一点上可能会感到困惑,因为访问变量有按值和按引用两种方式,而参数只能按值传递。
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,
或者用ECMAScript 的概念来说,就是arguments 对象中的一个元素)。在向参数传递引用类型的值时,会把
这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。请看下面
这个例子:
<!DOCTYPE html>
<html>
<head>
<title>Function Arguments Example 1</title>
<script type="text/javascript">
function addTen(num) {
num += 10;
return num;
}
var count = 20
var result = addTen(count);
alert(count); //20
alert(result); //30
</script>
</head>
<body>
</body>
</html>
这里的函数addTen()有一个参数num,而参数实际上是函数的局部变量。在调用这个函数时,变
量count 作为参数被传递给函数,这个变量的值是20。于是,数值20 被复制给参数num 以便在addTen()
中使用。在函数内部,参数num 的值被加上了10,但这一变化不会影响函数外部的count 变量。参数
num 与变量count 互不相识,它们仅仅是具有相同的值。
假如num 是按引用传递的话,那么变量count的值也将变成30,从而反映函数内部的修改。当然,使用数值等基本类型值来说明按值
传递参数比较简单,但如果使用对象,那问题就不怎么好理解了。再举一个例子:
<!DOCTYPE html>
<html>
<head>
<title>Function Arguments Example 2</title>
<script type="text/javascript">
function setName(obj) {
obj.name = "Nicholas";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
</script>
</head>
<body>
</body>
</html>
以上代码中创建一个对象,并将其保存在了变量person 中。然后,这个变量被传递到setName()
函数中之后就被复制给了obj。在这个函数内部,obj 和person 引用的是同一个对象。换句话说,即
使这个变量是按值传递的,obj 也会按引用来访问同一个对象。于是,当在函数内部为obj 添加name
属性后,函数外部的person 也将有所反映;因为person 指向的对象在堆内存中只有一个,而且是全
局对象。有很多开发人员错误地认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明
参数是按引用传递的。为了证明对象是按值传递的,我们再看一看下面这个经过修改的例子:
function setName(obj) {
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas"
这个例子与前一个例子的唯一区别,就是在setName()函数中添加了两行代码:一行代码为obj
重新定义了一个对象,另一行代码为该对象定义了一个带有不同值的name 属性。在把person 传递给
setName()后,其name 属性被设置为"Nicholas"。然后,又将一个新对象赋给变量obj,同时将其name
属性设置为"Greg"。如果person 是按引用传递的,那么person 就会自动被修改为指向其name 属性值
为"Greg"的新对象。但是,当接下来再访问person.name 时,显示的值仍然是"Nicholas"。这说明
即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj 时,这
个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。
PS:可以把ECMAScript 函数的参数想象成局部变量。
4.1.4 检测类型
要检测一个变量是不是基本数据类型?typeof 操作符是最佳的工具。说得更具体一点,
typeof 操作符是确定一个变量是字符串、数值、布尔值,还是undefined 的最佳工具。如果变
量的值是一个对象或null,则typeof 操作符会像下面例子中所示的那样返回"object":
<!DOCTYPE html>
<html>
<head>
<title>Determining Type Example 1</title>
<script type="text/javascript">
var s = "Nicholas";
var b = true;
var i = 22;
var u;
var n = null;
var o = new Object();
alert(typeof s); //string
alert(typeof i); //number
alert(typeof b); //Boolean
alert(typeof u); //undefined
alert(typeof n); //object
alert(typeof o); //object
</script>
</head>
<body>
</body>
</html>
虽然在检测基本数据类型时typeof 是非常得力的助手,但在检测引用类型的值时,这个操作符的用处不大。
通常,我们并不是想知道某个值是对象,而是想知道它是什么类型的对象。为此,ECMAScript
提供了instanceof 操作符,其语法如下所示:
result = variable instanceof constructor
如果变量是给定引用类型(根据它的原型链来识别;第6 章将介绍原型链)的实例,那么
instanceof 操作符就会返回true。请看下面的例子:
alert(person instanceof Object); // 变量person 是Object 吗?
alert(colors instanceof Array); // 变量colors 是Array 吗?
alert(pattern instanceof RegExp); // 变量pattern 是RegExp 吗?
根据规定,所有引用类型的值都是Object 的实例。因此,在检测一个引用类型值和Object 构造
函数时,instanceof 操作符始终会返回true。当然,如果使用instanceof 操作符检测基本类型的
值,则该操作符始终会返回false,因为基本类型不是对象。
PS:使用typeof 操作符检测函数时,该操作符会返回"function"。在Safari 5 及
之前版本和Chrome 7 及之前版本中使用typeof 检测正则表达式时,由于规范的原
因,这个操作符也返回"function"。ECMA-262 规定任何在内部实现[[Call]]方法
的对象都应该在应用typeof 操作符时返回"function"。由于上述浏览器中的正则
表达式也实现了这个方法,因此对正则表达式应用typeof 会返回"function"。在
IE 和Firefox 中,对正则表达式应用typeof 会返回"object"。