写在前面
无论在哪种编程语言中,函数都是特别有意思的部分,但同时也是一个难点。在ECMAScript中,作为对象的函数也不例外,让人又爱又恨。这一章我们主要从多个方面聊一聊函数参数这一部分,至于函数像海一样深的其他部分我们有机会再聊。
函数的有些知识点是比较简单的,所以在进入正题之前,我们先简单介绍两点:函数的return
和函数的重载,因为函数的重载需要arguments
的知识,所以我们最后来理解函数的重载。
章节结构如下:
- 函数的return
- 函数参数
- 函数的重载
注:如没有另外说明,文中的函数均是指ECMAScript中的函数。
函数的return
return
顾名思义就是返回
,函数在执行完return
后就会立即停止并返回,就是说,return
后面的语句是永远也不会执行的。如下:
function functionReturn(){
return ‘over;
console.log(‘something after return‘);
}
something after return
是永远也不会执行的。
注:这里可能会有歧义产生。这里说的return
之后是紧接着return
之后,像这样是不包括的:
function fn() {
(function(){
for(var i in a){
return(i);
}
})();
console.log(‘hello world‘);
}
var a = [1, 2, 3];
fn();
这里有return
,后面的打印依然可以正常输出,这个应该不难理解。评论里网友韩子迟
有提出这个歧义,特此更正,同时也感谢韩子迟
的提出。
函数参数
关于函数参数我总结了8点,我们分开来说。
1. 函数对传递进来的参数个数没有限制,对传递进来的参数的数据类型也没有限制。即,如果形参有2个,但是传递进来的实参可以是1个,也可以是3个,而且参数类型也无所谓。
2. 每个函数在被调用的时候,其活动对象会获得两个特殊的变量,其中之一就是arguments
对象,在arguments
对象中保存着传递给函数的参数,在函数体内可以通过arguments
对象来访问这个参数数组,从而可以获得每一个实参,但是要注意的是,arguments
对象只是与数组类似的一个对象,它并不是Array
的实例。
看个例子:
function funcAdd(num1,num2){
console.log(num1+num2);
console.log(arguments);
}
funcAdd(100,200);
调用funcAdd
函数的时候,实参被保存在arguments
对象中,所以打印结果为300
,[100,200]
。我们可以像访问数组一样访问arguments
对象,如下:
function funcAdd(num1,num2){
console.log(num1+num2);
console.log(arguments[0],arguments[1],arguments[2]);
}
funcAdd(100,200);
因为只传递了两个参数,所以打印结果为,100
、200
、undefined
。
3. 由上面的例子,还可以得出:没有传递值的命名参数将赋值为undefined
。
4. 命名参数(叫形参应该好理解一些)不是必须的。
如下:
function funcAdd(){
console.log(‘传递给函数的两个参数是:‘+arguments[0]+‘和‘+arguments[1]);
}
funcAdd(1,‘para2‘);
在这个函数中,并没有显式的给出命名参数,而是使用的arguments
对象中的前两个值,打印结果是:传递给函数的两个参数是:1和para2
,所以命名参数并不是必须的。显式写出命名参数一是习惯使然,二是提供一种视觉上和编程上的便利。
5. 关于命名参数,其他编程语言比如C需要函数签名,即函数名、参数个数、参数类型、返回值等相关声明信息,之后调用函数时必须和函数签名一致,但是 ECMAScript中的函数没有函数签名。这一点在下面的函数的重载中会有介绍。同时,也正是因为没有ECMAScript没有函数签名,所以函数不能 实现重载。
6. 函数的arguments
对象有一个length
属性,可以看成是数组的长度(arguments
对象只是与数组类似的一个对象,它并不是Array
的实例),从而获取传递给函数的参数的个数
如下:
function funcAdd(){
console.log(arguments.length);
}
funcAdd();
funcAdd(1);
funcAdd(1,2);
这段代码只是打印出调用函数时传递给函数的参数个数,打印结果为:0``1``2
(之前写的是123,特此更正)。
7. 命名参数的值永远和arguments
中的值(arguments
中有值)保持同步,这是单向的,即arguments
中的值会使命名参数的值同步。
如下:
function funcAdd(num1,num2){
arguments[0]=20;
console.log(num1+num2);
}
funcAdd(10,10);
打印结果是:30
。因为对于命名参数和arguments
,函数调用的是arguments
。只是一般情况下有一个命名参数的值会保存到arguments
中的过程而已。
注:虽然命名参数的值永远和arguments
中的值(arguments
中有值)保持同步,但是它们的内存空间是独立的,并不共用同一个内存空间,只是结果两个值会同步而已。
8. ECMAScript中的所有参数传递都是按值传递,访问变量有按值访问和按引用访问两种方式。
首先说一下基本类型的复制。基本类型的复制是完全独立的,比如value1=value2
,在这里将value2
的值复制给了value1
,这两个值是完全独立的,随意修改其中一个,另一个值是不会发生改变的。
但是引用类型的复制就不一样了,和基本类型的复制不同的是,引用类型复制的是指针,这个指针指向的都是堆内存中的同一个对象,所以,如果修改一个复制的变量,另一个变量也会跟着发生变化。
var o1=new Object(),o2=o1;
o1.color=‘red‘;
o2.color=‘blue‘;
console.log(o1.color);
console.log(o2.color);
因为o1
和o2
引用的是同一个对象,所以修改其中一个引用,另一个跟着变化。所以打印结果分别为:blue``blue
。
接下来说值的传递。
基本类型值的传递和基本类型变量的复制一个道理,引用类型值的传递和引用类型值的复制一样,只是复制引用,对于函数来说,把函数外面的值传递给函数内部,就是把函数外部的值复制给函数内部,和一个变量复制一个变量的道理一样。
有了函数参数的一些理论,下面聊一聊函数重载。
没有重载
函数的重载是指函数名相同,但是形参列表(参数的个数、参数的数据类型)不相同。在其他编程语言比如C++(之前写的是C,C没有重载,特此更正)中,重载是可以实现的,但是在ECMAScript中,函数是没有重载的。先看一段代码:
function funcAdd(num1,num2){
console.log(num1+num2);
}
function funcAdd(num){
console.log(num);
}
funcAdd(100,200);
funcAdd(100);
在ECMAScript中,位于后面的同名函数会覆盖前面的函数,所以最终调用的函数是funcAdd(num)
,在执行console.log(funcAdd(100,100));
是,因为调用的函数形参只有一个,传入arguments
的是两个参数100
和200
,即arguments[0]
的等于100
,arguments[1]
的等于200
,但是函数调用时只传递arguments[0]
,为了验证我们单独看下面一段代码:
function funcAdd(num){
console.log(num);
console.log(arguments);
}
funcAdd(100,200);
打印结果为100
和[100,200]
,验证了上面的结论。
虽然没有重载,但是我们可以来模拟重载机制,即可以来检测数据长度和数据类型,从而执行不同的代码来模拟重载的实现。我们简单写一段代码:
function funOverride(){
if(arguments.length==1){
console.log(‘Hi, ‘+arguments[0]+‘.‘);
}else{
console.log(‘Please input one para.‘);
}
}
funOverride();
funOverride(‘Jim‘);
在这里,我们分别判断实参的个数从而决定不同的输出,这在某种程度上模拟了重载。当然,这只是一段简单的代码,要完全实现重载还差的很多。