函数的扩展

函数的扩展

  • 1.函数参数的默认值

    • 参数变量是默认声明的,所以不能用let或const再次声明。
    • 使用参数默认值时,函数不能有同名参数。
    • 参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
  • 2.函数参数默认值的位置

    • 通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
  • 3.函数的 length 属性

    • 指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
    • 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
  • 4.作用域

    • 一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
    1
    2
    3
    4
    5
    var x = 1;
    function f(x, y = x) {
    console.log(y);
    }
    f(2) // 2

    上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    var x = 1;
    function foo(x, y = function() { x = 2; }) {
    var x = 3;
    y();
    console.log(x);
    }
    foo() // 3
    x // 1
    ```
    **上面代码中,函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量y,y的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。**
    * 5.使用
    >* 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
    ```js
    function throwIfMissing() {
    throw new Error('Missing parameter');
    }
    function foo(mustBeProvided = throwIfMissing()) {
    return mustBeProvided;
    }
    foo()
    // Error: Missing parameter
  • 6.rest 参数

    • ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
    • 函数的length属性,不包括 rest 参数。
  • 7.严格模式
    • ES2016 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
  • 8.name 属性
    • 函数的name属性,返回该函数的函数名。
    • 需要注意的是,ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。
    • Function构造函数返回的函数实例,name属性的值为anonymous。
    • bind返回的函数,name属性值会加上bound前缀。
  • 9.箭头函数
    • 由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
    • (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
    • (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
    • (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
    • (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
      双冒号运算符
    • 函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
  • 10.尾调用优化

    • 尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
    • 优化,我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。
    • 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
    • 尾递归
    • 函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
      递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
      
      1
      2
      3
      4
      5
      6
      7
      function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
      if( n <= 1 ) {return ac2};
      return Fibonacci2 (n - 1, ac2, ac1 + ac2);
      }
      Fibonacci2(100) // 573147844013817200000
      Fibonacci2(1000) // 7.0330367711422765e+208
      Fibonacci2(10000) // Infinity
  • 10.函数参数的尾逗号

    • ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。