函数扩展

1.函数参数的默认值

function fn(x,y){
        y = y || 2
        console.log(x,y) // 1 2
    }
    fn(1)

上面代码中,如果函数fn的参数y没有的话,则制定默认值位2。这种写法的缺点在于,如果参数y赋值了,但对应的布尔值为false,则该赋值不起作用

es6允许为函数的参数设置默认值,即直接写在参数定义的后面

function fn(x,y = 2){
        console.log(x,y) // 1 2
    }
    fn(1)

,ES6 的写法还有两个好处:首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;其次,有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。

  • 使用参数默认值时,函数不能有同名参数。
function fn(x,x){
        console.log(x,x) // 2  2
    }
    fn(1,2)
    function fn1(x,x=3,){
        console.log(x,x) // Duplicate parameter name not allowed in this context
    }
    fn1(1)
  • 与解构赋值结合使用
   let obj = {name: 111}
    function fn(obj){
        console.log(obj) // {name: 111}
    }
    fn(obj)
    function fn1({name}){
        console.log(name) // 111
    } 
    fn1(obj)
    function fn2({name}){
        console.log(name) // error
    }
    fn2()

上面代码使用了对象的结构赋值,只有当函数的参数是一个对象时,变量name才会通过解构赋值生成。如果函数调用时没有提供参数,变量name就不会生成,从而报错,通过函数参数的默认值,就可以避免这种情况

function fn2({name}={}){
        console.log(name) // undefined
    }
    fn2()

2. 参数默认值的位置

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

    function fn(x = 1,y) {
        console.log(x,y)
    }
    fn()  // 1 undefined
    fn(undefined,1)  // 1 1
    fn(2)  // 2 undefined
    fn(,1)  // Uncaught SyntaxError: Unexpected token ,报错

3.函数的length属性

指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。

    console.log((function (a) {}).length)  // 1
    console.log((function (a = 5) {}).length)  // 0
    console.log((function (a, b, c = 5) {}).length)  // 2

上面代码中,length属性的返回值,等于函数的参数个数减去指定了默认值的参数个数。比如,上面最后一个函数,定义了 3 个参数,其中有一个参数c指定了默认值,因此length属性等于3减去1,最后得到2。

这是因为length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了

4.作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。

    let a = 1;
    function fn(a,b = a) {
        console.log(a,b) // 2  2
    }
    fn(2)

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

    let a = 1;
    function fn(b = a) {
        console.log(b) // 1
    }
    fn()

上面代码中,函数fn调用时,参数b=a形成一个单独作用域,在这个作用域里面变量a没有定义,会向外面找,找到全局变量a,如果此时变量a不存在就会报错

5.rest函数

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

    function fn(...value) {
        console.log(value) // [2, 3, 4, 5]
    }
    fn(2,3,4,5)

注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

6.name 属性

函数的name属性,返回该函数的函数名。

    function fn(params) {
        
    }
    console.log(fn.name)  // fn

7.箭头函数

var f = v => v;

// 等同于
var f = function (v) {
  return v;
};
var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

使用注意点

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

8.双冒号运算符

箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。

函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。

9.尾调用优化

什么是尾调用优化?

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

 function fn1(x){
        console.log(x)
    }
    function fn(x){
        return fn1(x)
    }
    fn(2)

上面代码种,函数fn在最后一步调用的是函数fn1,这就叫尾调用

以下几种情况都不属于尾调用

// 情况一
function f(x){
  let y = g(x);
  return y;
}

// 情况二
function f(x){
  return g(x) + 1;
}

// 情况三
function f(x){
  g(x);
}

上面代码中,情况一是调用函数g之后,还有赋值操作,所以不属于尾调用,即使语义完全一样。情况二也属于调用后还有操作,即使写在一行内。情况三等同于下面的代码。

尾调用不一定是在函数尾部,只要是在最后异步操作即可

function fn(x){
    if(x>2){
        return fn1()
    }
    return fn2()
}
fn(3)

上面代码种函数fn1,fn2都属于尾调用,因为他们都是函数fn的最后一步操作

尾调用优化

尾调用之所以与其他调用不同,就在于它的特殊的调用位置。

我们知道,函数调用会在内存形成一个“调用记录”,又称“调用帧”(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用帧,就形成一个“调用栈”(call stack)。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。

10.尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

猜你喜欢

转载自www.cnblogs.com/mengxiangji/p/10692265.html
今日推荐