函数参数

以下为《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.length; i++) {
    alert("Hi, " + arguments[i])
  }
}
sayHi("Cat", "Alice")  // 'Hi, Cat', then 'Hi,
Alice'

所有的参数都包含在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
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容

  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,226评论 0 4
  • arguments 稍微学过点编程语言的人应该都知道函数、形参和实参的概念: 在JS中使用函数的时候,函数内部都会...
    亚历山大猫阅读 938评论 0 0
  • 本章内容 使用对象 创建并操作数组 理解基本的 JavaScript 类型 使用基本类型和基本包装类型 引用类型的...
    闷油瓶小张阅读 677评论 0 0
  • 在js中,函数本身属于对象的一种,因此可以定义、赋值,作为对象的属性或者成为其他函数的参数。函数名只是函数这个对象...
    bjhu电net阅读 529评论 0 5
  • (1) 概述 函数就是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。 (2) js声...
    woow_wu7阅读 1,663评论 0 0