内容要点:
一.可选形参
当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。
例如:
//将对象o中可枚举的属性名追加至数组
//如果省略a,则创建一个新数组并返回这个新数组。
function getPropertyNames(o,/*optional*/a){
if(a === undefined) a= [];
for(var property in o) a.push(property);
return a;
}
//这个函数调用可以传入1个或2个实参
var a = getPropertyNames(o); //将o的属性存储到一个新数组中
getPropertyNames(p,a); //将p的属性追加至数组a中
需要注意的是,当用这种可选实参来实现函数时,需要将可选实参放在实参列表的最后。那些调用你的函数的程序员是没办法省略第一个实参并传入第二个实参的,它必须将undefined作为第一个实参显式传入。同样注意在函数定义中使用注释/*option*/来强调形参是可选的。
if(a ===undefined ) a = []; //如果未定义,则使用新数组
相等于 a = a || [];
"||"运算符,如果第一个实参是真值的话就返回第一个实参;否则返回第二个实参。在这个场景下,如果作为第二个实参传入任意对象,那么函数就会使用这个对象。如果省略掉第二个实参(或者传递null以及其他任何假值),那么就创建一个空数组,并赋值给a。
需要注意的是,使用 "||"运算符代替if语句的前提是a必须预先声明,否则a=a||[]会报引用错误,在这个例子中a是作为形参传入的,相当于var a,即已经声明了a,所以这样用是没有 问题的。
二.可变长的实参列表:实参对象
当调用函数的时候传入的实参个数超过函数定义时的形参个数时,没有办法直接获得未命名值得引用。参数对象解决了这个问题。
在函数体内,标识符arguments是指向实参对象的引用,实参对象是一个类数组对象,这样可以通过数字下标就能访问传入函数的实参值,而不用非要通过名字来得到实参。
实参对象在很多地方都非常有用,下面的例子展示了使用它来验证实参的个数,从而调用正确的逻辑,因为JS本身不会这么做:
function f(x,y,z){
//首先,验证传入实参的个数是否正确
if(arguments.length!=3){
throw new Error("function f called with" + arguments.length +"arguments,but it expects 3 arguments");
}
//再执行函数的其他逻辑...
}
需要注意的是,通常不必这样检测实参个数。大多数情况下JS的默认行为是可以满足需要的:省略的实参都将是undefined,多出的参数会自动省略。
实参对象有一个重要的用处,就是让函数可以操作任意数量的实参。下面的函数就可以接收任意数量的实参,并返回传入实参的最大值
function max(/* ... */){
var max = Number.NEGATIVE_INFINITY;
//遍历实参,查找并记住最大值
for(var i =0 ;i < arguments.length; i++){
if(arguments[i]>max) max = arguments[i];
//返回最大值
return max;
}
}
var largest = max(1,10,100,2,3,1000,4,5,10000,6); //=>10000
类似这种函数可以接收任意个数的实参,这种函数也称为 "不定实参函数" ,这个术语源自古老的C语言
注意,不定实参函数的实参个数不能为零,arguments[]对象最适合的应用场景是在这样一类函数中,这类函数包含固定个数的命名和必需参数,以及随后个数不定的可选实参。
记住,arguments并不是真正的数组,它是一个实参对象。每个实参对象都包含以数字为索引的一组元素以及length属性,但它毕竟不是真正的数组。
数组对象包含一个非同寻常的特性。在非严格模式下,当一个函数包含若干形参,实参对象的数组元素是函数形参所对应实参的别名,实参对象中以数字索引,并且形参名称可以认为是相同变量的不同命名。
通过实参名字来修改实参值的话,通过arguments[]数组也可以获取到更改名的值,下面这个例子清楚地说明了这一点:
function f(x){
console.log(x); //输出实参的初始值
arguments[0] = null; //修改实参数组的元素同样会修改x的值
console.log(x); //输出"null"
}
如果实参对象是一个普通数组的话,第二条console.log(x)语句的结果绝对不会是null,
在这个例子中,arguments[0]和x指代同一个值,修改其中一个的值会影响到另一个。
在ES5中移除了实参对象的这个特殊特性。在严格模式下还有一点(和非严格模式下相比的)不同,在非严格模式下,函数里的arguments仅仅是一个标识符,在严格模式中,它变成了一个保留字。严格模式中的函数无法使用arguments作为形参名或局部变量名,也不能给arguments赋值。
callee和caller属性
除了数组元素,实参对象还定义了callee和caller属性。在ES5严格模式中,对这两个属性的读写操作都会产生一个类型错误。而在非严格模式下,ES标准规范规定callee属性指代当前正在执行的函数。通过caller属性可以访问调用栈。callee属性在某些时候会非常有用,比如在匿名函数中通过callee来递归地调用自身。
var factorial = function(x){
if(x<=1) return 1;
return x*arguments.callee(x-1);
};
三.将对象属性用做实参
当一个函数包含三个形参时,对于程序员来说,要记住调用函数中实参的正确顺序实在让人头疼。
为了解决这个问题,最好通过名/值对的形式来传入参数,这样参数的顺序就无关紧要了。
为了实现这种风格的方法调用,定义函数的时候,传入的实参都写入一个单独的对象之中,在调用的时候传入一个对象,对象中的名/值对是真正需要的实参数据。
例如:
//将原始数值的length元素复制至目标数组
//开始复制原始数组的form_start元素
//并且将其复制至目标数组的to_start中
//要记住实参的顺序并不容易
function arraycopy(/*array*/ from,/*index*/from_start,/*array*/to,/*index*/to_start,/*index*/length){
for(var i=from_start;i<length;i++){
to[to_start]=from[i];
to_start++;
}
}
//这个版本的实现
function easycopy(args){
arraycopy(args.from,args.from_start || 0,args.to,args.to_start || 0,args.length);
}
var a = [1,2,3,4],b=[];
easycopy({to:b,length:4,from:a,from_start:2});
console.log(a); //[1,2,3,4]
console.log(b); //[3,4]
四.实参类型
JS方法的形参并未声明类型,在形参传入函数体之前也未做任何类型检查。
可以采用语义化的单词给函数实参命名,或者像刚才的示例代码中的arraycopy()方法一样给实参补充注释,以此使代码自文档化,对于可选的实参来说,可以在注释中补充一下“这个参数是可选的”。
当一个方法可以接收任意数量的实参时,可以使用省略号:function max( /*number...*/ ){ /* 代码区 */ }
两个函数:
//返回数组(或类数组对象)a的元素的累加和
//数组a中必须为数字、null和undefined的元素都将忽略
function sum(a){
if(isArrayLike(a)){
var total = 0;
for(var i=0;i<a.length;i++){
var element =a[i];
if(element ==null) continue;
if(isFinite(element)) total +=element;
else throw new Error("sum():elements must be finite numbers");
}
return total;
}
else throw new Error("sum():argument must be array-like");
}
//isArrayLike函数
function isArrayLike(o){
if(o && //o非null、undefined等
typeof o === "object" && //o是对象
isFinite(o.length) && //o.length是有限数组
o.length>=0 && //o.length为非负数
o.length===Math.floor(o.length) && //o.length是整数
o.length<4294967296) //o.length< 2^23
return true; // o是类数组对象
else
return false; //否则它不是
}
//JS是一种非常灵活的弱类型语言,有时适合编写实参类型和实参个数的不确定性的函数。
//flexisum()方法:它可以接收任意数量的实参,并可以递归地处理实参是数组的情况,
//这样的话,它就可以用做 不定实参函数或者实参是数组的函数。
//此外,这个方法尽可能的在抛出异常之前将非数字转换为数字:
function flexisum(a){
var total = 0;
for(var i =0;i<arguments.length;i++){
var element = arguments[i],n;
if(element ==null) continue; //忽略null和undefined实参
if(Array.isArray(element)) //如果实参是数组
n=flexisum.apply(this,element);
else if(typeof element === "function") //否则,如果是函数
n=Number(element()); //调用它并做类型转换
else
n=Number(element); //否则直接做类型转换
if(isNaN(n)) //如果没法转换为数字,则抛出异常
throw Error("flexisum():can't convert"+element+"to number");
total +=n; 否则,将n累加到total
}
return total;
}
//var g =[1,2,null,undefined];//3
//var g =[1,2,null,undefined,"123"];//126
//var g =[1,2,null,true];//4
//var g =[1,2,null,[{}]];//Error: flexisum():can't convert[object Object]to numbe
// var g =[1,2,null,undefined,{a:1,b:2,c:3}] //Error: flexisum():can't convert[object Object]to number
//var g =[1,2,null,true,function(){console.log("error")}];//Error: flexisum():can't convertfunction (){console.log("error")}to number
//var g =[1,2,null,undefined,[1,2,3]] //9
var hh=flexisum(g);
console.log(hh);