参数(argument)与函式参数(parameter)

在讨论函式时,很多人都会把这两个搞混,我自己也不例外。

虽然讲错别人也听得懂,但是我们还是要搞清楚这两个的定义到底是什么!

 

参数是当我们呼叫函式时传递给它的值

参数是我们在函式定义中所列出的变量
看完还是有点不懂?没关系,上图!


声明式(declaration)与表达式(expression)

发现在前几天介绍声明函式时没有特别说明两者名称的差异,虽然这跟上面的参数与参数一样说错别人也懂(yjssqsdg),但我们还是要分清楚喔!

 

声明式:

 

function funA(){

}

表达式:

 

var myFun = function(){

}

隐含参数

在呼叫函式时候除了传递自己给的参数外,你知道还会传递两个隐含参数:arguments和this吗?

 

什么事「隐含」,就是这些参数没有明确列在函式署名(function signature)中,只会默默传递给函式,并且可以在函式内存取他们,也可以在函式中被引用。

 

我们今天只会先介绍arguments,this会在之后的篇幅中提到。

 

arguments

是传递给函式的所有参数之集合,不用管相对应的参数是否有明确定义。这也让我们可以实现JS本身不支持的函式重载(function overloading)及可变参数数量的可变函式(variadic function)。

 

讲到这边可能会有人困惑说什么是函式重载?所谓函式重载就是函式的名称都一样,但是会根据不同的输入拥有不同的输出。

看不太懂也没关系,在我们讲完arguments后会提到怎么实作。

 

我们先来看一个简单的例子:

 

function show(a,b,c){

console.log(a,b,c);

console.log(arguments);

console.log(arguments[0]);

console.log(arguments.length);

console.log(typeof arguments);

}

show(1,2,3);

 

可以看到arguments储存了所有传递参数的值。而且还可以用lenght属性,但这边要注意到,它并不是一个数组,是一个叫做类数组的东西,也就是说无法使用JS提供给array的函式库。

 

function show(a,b,c){

arguments.sort(function(a,b){

return a - b;

});

}

show(1,2,3);//Uncaught TypeError: arguments.sort is not a function

这边要注意到一点,如果我们修改了arguments的值,原本的参数也会被修改:

 

function modifyArg(a,b,c){

console.log(a,b,c);//1 2 3

arguments[0] *= 20;

console.log(a,b,c);//20 2 3

}

modifyArg(1,2,3);

在前面有提到过说可以利用arguments作可变函式:

 

function dynamicArg(){

console.log(arguments);

}

dynamicArg(1,2,3);//Arguments(3)[1,2,3,callee:ƒ,Symbol(Symbol.iterator):ƒ]

再来,我们来看一下到底怎么作函式重载呢:

 

function mutli(){

let argLength = arguments.length;

if(argLength ===1){

console.log(argLength[0])

}else if(argLength ===2){

console.log(argLength[0],argLength[1])

}

}

这是最简单的函式重载,虽然可以运作但实在看了头有点痛。

 

我们今天来假设一个情境,我们想要有一个函式可以对人名进行搜索,根据不同的参数长度回传搜寻的值,这时候我会就会需要一个object物件:

 

var people = {

name:[“Dean Edwards”,“Alex Russell”,“Dean Tom”,“Alex Tsai”,“Tom Cruise”,“Tom Ford”]

}

我们想要能find('Dean')时回传[“Dean Edwards”,“Dean Tom”];

find()时回传所有人;find('Tom Cruise')时回传[“Tom Cruise”]。

 

于是我们需要一个函式:

 

var people = {

name:[“Dean Edwards”,“Alex Russell”,“Dean Tom”,“Alex Tsai”,“Tom Cruise”,“Tom Ford”]

}

 

function addMethod(obj,key,fn){

var old = obj[key];

obj[key] = function(){

if(fn.length === arguments.length){

return fn(arguments);

} else if(typeof old ===“function”){

return old(arguments);

}

}

}

接着,我们就可以写搜索人名的函式了:

 

//没有参数传入,回传所有人

function findAll(){

return people.name;

}

 

//传入一个参数时,回传firstName相同的人名数组

function findFirstName(firstName){

var ret = [];

for(var i = 0;i < people.name.length;i++){

if(people.name[i].indexOf(firstName)!== -1){

ret.push(people.name[i]);

}

}

return ret;

}

 

//传入两个参数时,回传firstName和lastName都相同的人名数组

function findFullName(firstName,lastName){

var ret = [];

for(var i = 0;i < people.name.length;i++){

if(people.name[i] ===(firstName +“”+ lastName)){

ret.push(people.name[i]);

}

}

return ret;

}

写完之后,将他们经由addMethod()传入people物件里面:

 

addMethod(people,“find”,findAll);

 

addMethod(people,“find”,findFirstName);

 

addMethod(people,“find”,findFullName);

之后就可以用了:

 

console.log(people.find());//[“Dean Edwards”,“Alex Russell”,“Dean Tom”,“Alex Tsai”,“Tom Cruise”,“Tom Ford”]

console.log(people.find(“Tom”));//[“Tom Cruise”,“Tom Ford”]

console.log(people.find(“Dean Edwards”));//[“Dean Edwards”]

或许有人会很困惑,为什么我们可以这样用,让我们来逐一分析这个addMethod函式:

 

function addMethod(obj,key,fn){

var old = obj[key];

obj[key] = function(){

if(fn.length === arguments.length){

return fn(arguments);

} else if(typeof old ===“function”){

return old(arguments);

}

}

}

可以看到我们每次呼叫时,都会先储存原先的people.find到变量old,这时候就会造成闭包的现象(LeLeyuesao),这我们后面篇幅会提到,简单的说,我们可以藉由people.find不断往上找,直到找不到为止。

 

所以当我们呼叫people.find(“Tom”)时,会执行:

 

if(fn.length === arguments.length){

return fn(arguments);

}

else if(typeof old ===“function”){

return old(arguments);

}

这时候的fn是findFullName但由于传入参数长度跟findFullName长度不相等,所以会执行:

 

else if(typeof old ===“function”){

return old(arguments);

}

这时候的old等于people.find(“Tom”),但是里面包附的fn已经换成,

findFirstName由于传入参数长度跟findFirstName长度相等,便会直接执行,不会在往上找。

 

或许会有人认为,我为了写一个函式重载,还得写这么多复杂的函式,不如需要时在呼叫就好了啊!

 

但是函式重载的好处在于,只需要苦过一次,后面就可以用短短的一行函式就得到想要的结果了!

 

参考资料:

忍者JavaScript开发技巧探秘

JavaScript中的函数重载(Function overloading)