函数参数

以下为《JavaScript.info》教程的部分译文。

函数参数
1.访问未命名参数
2.arguments的核心
3.像数组一样使用参数对象arguments
4.构造一个真正的数组
5.默认值
6.关键字参数
7.arguments的特殊属性
1.arguments.callee
2.arguments.callee.caller
在JavaScript中,不论函数定义时列出几个参数,在被调用时都可以传任何数量的参数。
例如:

function go(a,b) {
  alert("a="+a+", b="+b)
}
go(1)     // a=1, b=undefined
go(1,2)   // a=1, b=2
go(1,2,3) // a=1, b=2, extra argument is not listed

函数调用时没提供的参数被认为是未定义的;所以我们看以下函数是否是无参数的情况下被调用:

function check(x) {
  alert(x === undefined) // ok now I know there is no x
}
 check()

没有语法上的多态性
在一些语言中,程序员可以写2个名字相同而参数列表不同的函数,而解释器/编译器会自动匹配正确的函数:

function log(a) {
  ...
}
function log(a,b,c) {
  ...
}
log(a) // first function is called
log(a,b,c) // second function is called

这个称为函数的多态性。在JavaScript中,并没有这回事。
只会有一个名字为log的函数,不论调用时传入任何参数,都只会调用那一个。

访问未命名参数
那我们如何获得比参数中所列参数个数更多的参数呢?
在每个函数里面有一个特殊的伪数组,叫做arguments。可用数字标识到arguments中包含的所有参数,比如arguments[0], arguments[1]等。
这里是一个例子,不论函数有多少参数,都可以列出来:

function sayHi() {
  for(var i=0; i

所有的参数都包含在arguments对象中,尽管其中的一些函数名称形如:sayHi(a,b,c)。

arguments的核心
一个初学者经常犯的错误就是在arguments上使用数组对象的方法,简单的说,你不能如下使用:

function sayHi() {
  var a = arguments.shift() // error! arguments is
not Array
  alert(a)
}
sayHi()

arguments并不是数组,那它是什么呢?让我们使用[[Class]]属性来看一下:

(function() {
  alert( {}.toString.call(arguments) )  // [object
Arguments] 
})()

类型几乎和对象一样,它只是看起来是数组,因为它的键(keys)是数字而且有length,然而没有其他相似处了。

像数组一样使用arguments
依然有一个方法可以在arguments对象上调用数组的方法:

function sayHi() {
  var args = [].join.call(arguments, ':')
  alert(args)  // 1:2:3
}
sayHi(1,2,3)

这里我们在arguments上执行数组的join方法,“:”作为第一个参数。方法可以运行,因为内置的大多数数组方法(包含join)都支持数字索引和length。如果格式正确,join方法也可以使用在一个自定义对象上:

var obj = { 0: "A", 1: "B", length: 2 }
 alert( [].join.call(obj) ) // "A,B"

构造一个真正的数组
arr.slice(start,end)可以把arr从start到end的部分复制到一个新的数组。
这个方法也可以在arguments的上下文中被调用,把其中所有元素复制到一个真实的数组中去:

function sayHi() {
  var args = [].slice.call(arguments) // slice without parameters copies all
  alert( args.join(':') ) // now .join works
}
sayHi(1,2)

明白了!'arguments'是参数
arguments和命名参数引用同样的值。
更新arguments[..]引发对应参数的改变,反之亦然。
举例:

f(1)
function f(x) {
  arguments[0] = 5
  alert(x) // 5, updating arguments changed x
}

相反的过程:

f(1)
function f(x) {
  x = 5
  alert(arguments[0]) // 5, update of x reflected in arguments
}

Arry方法修改arguments的同时也修改局部参数:

sayHi(1)
function sayHi(x) {
  alert(x)           // 1
  [].shift.call(arguments)  
  alert(x)           // undefined, no x any more :/  
}

实际上,在现代的ECMA-262 5 规范中,arguments是从局部变量中分离出来的。
到目前为止,浏览器仍然按照上述过程执行。可以尝试一下例子看看。
一般来说,不去修改arguments是一个良好的习惯。
默认值
如果你想函数的参数可选,有以下两种方法:
1.第一,检查是否为undefined并重新分配:

function sayHi(who) {
  if (who === undefined) who = 'me' 
  alert(who)  
}

2.或者,使用或||运算符:

function sayHi(who) {
  who = who || 'me'  // if who is falsy, returns 'me'
    
  alert(who)  
}
sayHi("John")
sayHi()  // 'me'

这种方式适用于没有提供参数和提供参数两种情况,在实际应用中,通常为这种情况。

众所周知,Math.max是一个包含可变数量参数的函数,它返回最大值或者参数:

alert( Math.max(1, -2, 7, 3) )

利用func.apply(context, args)和Math.max寻找数组中的最大元素:

var arr = [1, -2, 7, 3]
/* your code to output the greatest value of arr */

解决途径
解决途径:

var arr = [1, -2, 7, 3]
alert( Math.max.apply(Math, arr) ) // (*)

这里,我们调用Math.max方法,传递参数的数组args。
在“自然”调用Math.max(...)过程中,上下文this设定为Math,即"."前面的对象。在我们的代码中我们通过显示传递参数到apply来保持一致。
实际上,Math.max这个方法根本没有用上下文。我们可以进一步简化我们的代码:

var arr = [1, -2, 7, 3]
alert( Math.max.apply(0, arr) ) // dummy '0' context

关键字参数
想象一下,如果你获得了一个含有许多参数的函数,而且大多数参数有默认值。如下所示:

function showWarning(width, height, title, contents, showYesNo) {
  width = width || 200; // default values
  height = height || 100;
  var title = title || "Warning";
  ...
}

这里我们获得了一个用于显示警告窗口的函数,它可以设置宽度、高度、标题、文本内容,显示按钮(如果设置了showYesNo)。
大多数参数具有默认值。
下面是我们怎么使用的:

showWarning(null, null, null, "Warning text", true)
// or
showWarning(200, 300, null, "Warning text")

问题是:人们容易忘记参数的顺序和含义。
想象一下你有10个参数,而且大多数为可选参数。函数调用过程会变得十分可怕。
关键字参数技术存在于Python, Ruby 和其它语言当中。
在JavaScript中,这种技术通过一个参数对象实现:

function showWarning(options) {
  var width = options.width || 200  // defaults 
  var height = options.height || 100
  var title = options.title || "Warning"
  // ...
}

调用这只能函数变得简单。你只要像这样传递一个参数的对象:

showWarning({ 
  contents: "Text", 
  showYesNo: true
})

另外一个好处就是这个参数对象能够重新配置和重新使用:

var opts = {
  width: 400,
  height: 200, 
  contents: "Text", 
  showYesNo: true
}
showWarning(opts)
opts.contents = "Another text"
showWarning(opts) // another text with same options

关键字参数应用于大多数框架。
arguments的特殊属性
arguments.callee
arguments有一个有趣的属性arguments.callee,它引用正在运行的函数。
为了支持命名函数表达式和更好的性能,这个属性已经被ECMA-262弃用。
如果他们知道arguments.callee不是必须的,JavaScript 的实现可以进一步优化代码。
一般情况下这个属性不会出现问题,但在“严格模式”下,启用这个属性会报错。

通常,这个属性应用于匿名函数的递归。
举例如下,setTimeout(func, ms)是一个在ms毫秒后调用func的内置函数。

setTimeout(  
  function() { alert(1) }, // alerts 1 after 1000 ms (=1 second)
  1000
)

这个函数没有名称,让我们使用arguments.callee递归调用3次:

var i = 1
setTimeout(  
  function() { 
    alert(i) 
    if (i++<3) setTimeout(arguments.callee, 1000)
  }, 
  1000
)

另外一个例子是阶乘的:

// factorial(n) = n*factorial(n-1)
var func = function(n) {  
  return n==1 ? 1 : n*arguments.callee(n-1)  
}

上述的阶乘函数在func重新分配的情况下仍然会运行,这是由于它使用arguments.callee引用了自身。
arguments.callee.caller
arguments.callee.caller属性保持对调用函数的引用。

出于和arguments.caller同样的原因,这个属性已经被ECMA-262弃用。arguments.caller属性的意义与其相同,但兼容性更差。不要使用arguments.caller,尽量使用 arguments.callee.calle,所有浏览器都有这个属性。
在下面的例子中,arguments.callee.caller引用了g的调用函数,调用函数为f。

f()
function f() {
  alert(arguments.callee.caller) // undefined
  g()
}
function g() {
  alert(arguments.callee.caller) // f
}

你可能感兴趣的:(函数参数)