ES6_函数的扩展

184 阅读7分钟

ES6_函数的扩展

一、参数默认值

1. 参数默认值基本使用

function log(x, y = 'world') {
    console.log(x, y);
}
log('hello');   // hello world

2. 与解构赋值默认值结合使用

// 写法1
function fun1 ({x = 0, y = 0} = {}) {
    return [x, y];
}

// 写法2
function fun2 ({x, y} = {x: 0, y: 0}) {
    return [x, y];
}

上面两种写法都对函数的参数设定了默认值,区别在于,写法一中函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;即如果没有传参数,函数参数的默认值就会生效,然后才是解构赋值的默认值生效。写法二中函数参数的默认值是一个有具体属性的函数,但是没有设置对象解构赋值的默认值。

3. 参数默认值的位置

通常定义了默认值的参数应该是函数的为参数。如果非尾部的参数设置了默认值,实际上这个参数是无法省略的。要想使用默认值,每次都要传递undefined

4. 留意暂时性死区

形参和ES6中的let或const声明一样,具有作用域,并且根据参数的声明顺序,存在暂时性死区

function test (a = b, b) {
    console.log(a, b);
}
test(1, 2); // 因为a传值了,所以不会使用默认值。打印1 2

test(undefined, 2); 
// 因为a没传值,所以要用b,但是这时候b是在a的后面声明的,产生临时死区,所以会报错

5. 默认值对函数length属性的影响

指定了默认值以后,函数的length属性将返回没有指定默认值的参数个数。即:length属性返回函数参数个数减去指定了默认值的参数个数。

这是因为length属性的含义是该函数预期传入的参数个数。

注意:如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数

(function (a, b = 1, c) {}).length;     // 1

6. 参数默认值对作用域的影响

函数设置了参数默认值,函数在进行初始化时,参数会形成一个单独的作用域。等到初始化结束,这个作用域就会消失。

7. 参数默认值对arguments的影响

// 严格模式下,arguments与形参脱离
test(1, 2);
function test(a, b) {
    console.log(a, b);      // 1, 2
    console.log(arguments[0], arguments[1]);        // 1, 2
    a = 3;
    console.log(a, b);      // 3, 2
    console.log(arguments[0], arguments[1]);        // 1, 2
}

// 非严格模式下,arguments与形参不脱离
function test(a, b) {
    console.log(a, b);      // 1, 2
    console.log(arguments[0], arguments[1]);        // 1, 2
    a = 3;
    console.log(a, b);      // 3, 2
    console.log(arguments[0], arguments[1]);        // 3, 2
}

只要给函数加上参数默认值,该函数会自动变为严格模式下的规则:arguments和形参脱离

二、剩余参数(rest 参数)

当参数不确定个数的时候,其实也可以使用arguemnts。但是这样有一点不好。因为当在严格模式下,arguments和形参是脱离的。所以如果不小心改变了形参,那么再使用arguments就会出错。其次,从语义上,使用arguments获取参数,由于形参缺失,无法从函数定义上理解函数的真实意图

ES6的剩余参数专门用于收集末尾的所有参数,将其放置到一个形参数组中。

1. 使用的细节:

  1. 一个函数,仅能出现一个剩余参数
  2. 一个函数,如果有剩余参数,剩余参数必须是最后一个参数

三、name属性

函数的name属性返回该函数的函数名。这个属性早就被浏览器广泛支持,但是知道ES6才写入标准。

// 将匿名函数赋值给一个变量
var f = function() {}
// ES5中:f.name = ""  得到空字符串
// ES6中:f.name = "f"

// 将具名函数赋值给一个变量,ES5、ES6都返回具名函数原本的名字

四、明确函数的双重用途

函数用两种用途,一种是直接普通调用,一种是new来使用。

过去可以通过instanceof来判断,但是这种方式不是很完备

ES6提供了一个特殊的API,可以使用该API在函数内部,判断该函数是否使用了new来调用

new.target
// 该表达式,得到的是:如果没有使用new来调用函数,则返回undefined
// 如果使用new调用函数,则得到的是new关键字后面的函数本身

function Person(name, age) {
    if (new.target === undefined) {
        throw new Error("该函数没有使用new来调用");
    }
    this.name = name;
    this.age = age;
}

五、箭头函数

1. 回顾之前的

在之前,函数的this和函数的调用方式有关

  1. 通过对象调用函数,this指向对象
  2. 直接调用函数,this指向全局对象
  3. 如果通过new调用函数,this指向新创建的对象
  4. 如果通过apply、call、bind调用函数,this指向指定的数据
  5. 如果是DOM事件函数,this指向事件源
const obj = {
    count: 0,
    start: function () {
        console.log(this);          // 这里的this是指向对象obj的
        setInterval(function() {
            //这里的this是指向window的,因为这个函数是js引擎内部直接调用的
            console.log(this); 
            this.count ++;
        }, 1000)
    }
}
obj.start();

如果不好理解this,可以把写成箭头函数的地方给换位this来看一看,就知道this指向什么了

const obj = {
    count: 0,
    start: function() { 
        console.log(this);
        setInterval (this.xxx);     
        // 直接把箭头函数直接替换为this,就会简单的看出这里的this指向对象obj了
        // 即:这里写的箭头函数的this也是指向对象obj的
    }
}

不是所以地方都是可以使用箭头函数的,如下:一般,对象的属性不用箭头函数

const obj = {
    count: 0,
    print: () => {
        console.log(this.count);    
    },
    print: this     
    // 可以把这里的箭头函数替换为this,由于这里的this不是写在obj的函数里的,所以这里的this
    // 指向window,所以以上箭头函数里的this指向window,当调用obj.print()的时候
    // 打印undefined。所以这里不适合使用箭头函数
}

2. 使用语法

箭头函数是一个函数表达式,理论上任何使用函数表达式的场景都可以使用箭头函数

如果箭头函数返回一个对象,就要加一个小括号:
const sum = (a, b) => ({
    a: a,
    b: b,
    sum: a + b
})

3. 注意细节

  1. 箭头函数的函数体中的this,取决于箭头函数定义的位置的this指向,而与如何调用无关(其实这里 也可理解为箭头函数中不存在this,使用的是外层this)。

  2. 箭头函数中,不存在this、arguments、new.target、super,如果使用了,则使用的是函数外层的对应的 this、arguments、new.target、super。

  3. 箭头函数没有原型

  4. 由于箭头函数没有原型,所以箭头函数不能作为构造函数使用

4. 应用场景

  1. 临时性使用的函数,并不会刻意调用(不会可以调用的意思是我们不会那这个函数名字来调用, 比如绑定事件、计时器里的函数。。。这些我们都是不会刻意拿函数名调用的)它,比如:

    1. 事件处理函数
    2. 异步处理函数
    3. 其他临时性的函数
  2. 为了绑定外层this的函数

  3. 在不影响其他代码的情况下,保持代码的简洁。最常见的,数组方法中的回调函数(map、reduce)。

六、尾调用优化

下午把《ES6标准入门》第7章“尾调用优化”部分好好研究了一下。之前看大佬的博客知道函数式编程难,看完发现确实难。虽然这部分看懂了,但是函数式编程的思想要想在开发中真正的使用,还是任重道远。所以这部分就先不总结了。等之后对函数式编程有了更深入的理解之后,再好好总结一下。