美文网首页
函数参数

函数参数

作者: jxyjxy | 来源:发表于2016-03-20 20:46 被阅读0次

    以下为《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
    }
    

    相关文章

      网友评论

          本文标题:函数参数

          本文链接:https://www.haomeiwen.com/subject/wtpjkttx.html