美文网首页
ES6学习之- 函数的扩展

ES6学习之- 函数的扩展

作者: 尤小小 | 来源:发表于2017-09-21 01:07 被阅读16次

    Part2 函数的扩展

    2.1 函数参数默认值,可以传任意类型

    函数名 = ([param1, ..., params=默认值]) => { }
    // 等同于
    function 函数名([param1, ..., params=默认值]) = { }

    2.1.1 es5采用变通方法实现函数参数默认值,为什么要给函数扩展参数默认值?

    function foo(a, b) {
      b = b || 5;
      console.log(a + b);
     }
    
    foo(1); // 6
    foo(1, 2); // 3
    foo(1, ''); // 6
    foo(1, 0); // 6
    

    上面代码检查函数doSomething的参数b有没有赋值,如果没有,则指定默认值为5。这种写法的缺点在于,如果参数b赋值了,但是对应的布尔值为false (0、-0、null、""、false、undefined 或者 NaN),则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值。 当然es5也可以去处理这几种特殊情况,不过es6给出更好的解决办法:给函数的参数扩展了默认值写法。

    2.1.2 es6函数参数默认值可以传任意类型

    es6 允许函数的参数设置为默认值,既直接写在参数定义的后面,先判断一下参数b是否被赋值,如果没有,再等于默认值,es6 允许函数的默认值可以传任意类型。

    // 参数默认值为数字
    function foo (a, b=5) {
        console.log(a + b);
    }
    foo(1); // 6
    
    
    // 参数默认值为字符串
    function foo (a, b='World') {
        console.log(a + b);
    }
    foo('Hello'); // "Hello World"
    
    
    // 参数默认值为对象
    function doSomething (a, b={x: 1, y: 2}) {
        console.log(a + b.x + b.y);
    }
    foo(1, {x: null, y: 2}); // 3
    
    
    // 默认值为解构赋值 (不建议)
    function foo ({x, y = 2}) {
        console.log(x, y);
    }
    foo({}) // undefined 2
    foo({x: 1}) // 1 2
    foo({x: 1, y: 3}) // 1 3
    foo() // TypeError: Cannot read property 'x' of undefined
    
    // 默认值为解构赋值:参数是一个对象 (推荐)
    function foo ({x, y = 2} = { }) {
        console.log(x, y);
    }
    foo() // undefined 2
    

    函数参数默认值可以使用传任意类型,以上展示了数值、字符串、对象、解构赋值的写法,这种写法优势是:便于阅读,利于优化。

    2.1.3 参数变量是默认声明时,不能用let或const再次声明。

    (function foo (a) {
        var a = 1;
        console.log(a);
    })()
    // 1
    
    ( function foo (b) {
        let b = 2;
        console.log(b);
    })()
    // 报错:Identifier 'b' has already been declared
    
    ( function foo (c) {
        const c = 3;
        console.log(c);
    })()
    // 报错:Identifier 'c' has already been declared
    

    以上例子,可以看出,在函数体中,不能用let或者const再次声明,否则会报错。

    (4) 使用参数默认值时,函数不能有同名参数。

    // 不报错
    function foo(x, x, y, y) {
      // ...
    }
    
    // 报错
    function foo(x, y, y = 1) {
      // ...
    }
    
    // 报错
    function foo(x, x, y = 1) {
      // ...
    }
    

    上面代码中,跟默认值参数名同名的时候报错,如果我跟默认值参数名不重名,跟其他的非默认值参数重名可以么?也是不可以的,只要使用了参数默认值,就不能有同名参数。

    (5) 参数默认值的位置--尾参数

    function foo(x, y = 2, z) {
      return [x, y, z];
    }
    
    foo(1, ,2); // 报错
    foo(1, undefined, 2); // [1, 2, 2]
    foo(1, null, 2); // [1, null, 2]
    

    如果默认值得参数不是尾参数,这时,无法只省略该参数,而不省略它后面的参数,除非显式输入undefined。传入undefined的,将触发该参数等于默认值,null则没有这个效果。为了避免这个问题,建议带参数默认值的函数,默认值参数设为尾参数。

    正确写法:

    function foo(x, z, y=5) {
      console.log(x, z, y);
    }
    

    2.2 rest参数,必须只能是最后一个参数

    rest参数说白了就是扩展运算符参数,形式为...x,代表实参为数组的传进来的x变量,用于获取函数的多余参数。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。由于rest参数是一个真正的数组,数组持有的方法都可以使用。

    function add (...items) {
      let s = 0;
    
      items.forEach(function(item) {
        s += item;
      });
    
      return s;
    }
    
    add(1,2,3); // 6
    

    在es5中arguments可以用来获取函数参数集合的类数组对象。由于arguments对象不是真正的数组,要想使用真正的数组方法,必须使用 Array.prototype.slice.call(arguments)转为数组。

    // arguments变量的写法
    function sortNumbers() {
      return Array.prototype.slice.call(arguments).sort();
    }
    sortNumbers(3, 1,  0, 2); // [0, 1, 2, 3]
    
    // rest参数的写法
    sortNumbers = (...numbers) => numbers.sort();
    sortNumbers(3, 1,  0, 2); // [0, 1, 2, 3]
    

    显而易见,rest 参数的写法更自然也更简洁。对于rest参数,就可以替代arguments类数组,不需要 arguments对象了。

    注意:rest 参数只能是最后一个参数,否则会报错

    // 报错:Rest parameter must be last formal parameter
    function foo(a, ...b, c) {
      // ...
    }
    
    // 报错:Rest parameter must be last formal parameter
    function foo(a, ...b, ...c) {
      // ...
    }
    
    function foo(a, b, ...c) {
      // ...
    }
    

    第一种情况,rest 参数没有放在最后的位置;第二种情况,虽然参数c是最后一个参数, 但是b却不是,就说明了rest参数只能有一个扩展运算符;第三种情况满足条件。

    注意:一个函数只能有一个rest参数,并且rest参数只能是最后一个参数。

    2.3 严格模式

    从es5开始,函数内部可以设定为严格模式。

    function foo(a, b) {
      'use strict';
      // code
    }
    

    es6 做了一点修改,规定只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

    // 报错
    function foo(a, b = a) {
      'use strict';
      // to do something
    }
    
    // 报错
    const foo = function ({a, b}) {
      'use strict';
      // to do something
    };
    
    // 报错
    const foo = (...a) => {
      'use strict';
      // to do something
    };
    

    两种方法可以规避这种限制。
    第一种是设定全局性的严格模式,这是合法的。

    // 第一种是设定全局性的严格模式
    'use strict';
    
    function foo (a, b = a) {
      // code
    }
    
    // 第二种是把函数包在一个无参数的立即执行函数里面
    foo = (function () {
      'use strict';
      return function (value = 42) {
        return value;
      };
    }());
    

    2.4 箭头函数 Arrow functions

    es6 中对函数的扩展多出了一个新东西“箭头函数”,可以说箭头函数是es6里使用频率最高的一个语法糖,箭头函数相当于匿名函数,并且简化了函数定义,es6 允许使用箭头 () => {} 定义函数。Arrow functions

    命名箭头函数:
    
    functionName =  ([param1, ...])  => {
        todoSomething
    }
    
    匿名箭头函数:
    
    ([param1, ...])  => {
        todoSomething
    }
    

    一条语句,不同的参数的写法如下:

    // 不传参数的箭头函数,使用一个圆括号代表参数部分
    {
        let foo = () => 5;
        foo (); // 5
    }
    
    // 只传一个参数的箭头函数,参数部分的圆括号可以省略
    {
      let foo = a => a;
      foo(5); // 5
    }
    
    // 传多个参数的箭头函数,使用圆括号代表参数部分
    {
    
      let foo = (a, b) => a + b;
      foo(5, 10); // 15,
    }
    

    箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }return。下面展示几个例子:

    // 包含多条语句的箭头函数
    {
      let foo = (a, b) => {
        console.log(a); // 5
        console.log(b); // 10
        console.log(this); // Window
        return a + b;
      };
      foo(5, 10); // 15
    }
    
    // 带有默认值参数的箭头函数
    foo = (a, b=5)  => a + b;
    foo(1); // 6
    
    // rest参数的箭头函数
    // 箭头函数不可以使用arguments对象,若想使用,可以用 rest 参数代替
    {
        let add = (...items) => {
            let s = 0;
            items.map( item =>  {
                s += item;
            });
    
            return s;
        }
        
        add(1,2,3); // 6
    }
    

    箭头函数很适合于无复杂逻辑或者无副作用的纯函数场景下,比如map, filter等回调函数定义中。

    2.5 箭头函数中的this

    2.5.1 普通函数中的this

    • 如果调用函数属于某个对象,那么函数内的this指向该对象
    • 在普通函数中,this指向它的直接调用者;如果找不到直接调用者, 则是函数独立调用,this则指向window
    • 在严格模式下,没有直接调用者的函数中的this是undefined
    • 如果是构造函数,那么函数内的this则指向实例化对象
    • 使用call, apply,bind绑定的,可以改变this的指向

    2.5.1 箭头函数中的this指向

    箭头函数里面的this, 跟es5里普通函数里的this不一样的,箭头函数中的this始终指向函数定义时的作用域的this。实际原因是箭头函数根本没有自己的this,导致内部this就是外层代码块的this。

    // 箭头函数中的this,在事件监听中的应用
    
    <button id="button">按钮</button>
    
    <script>
        (function (d, log) {
            let obj = {
                a: 5,
                clickHandle() {
                    log('这是事件监听内部')
                    log(this) // this指向obj这个对象
                },
                init() {
                    let btn = d.querySelector('#button');
                    let _this = this;
    
                    btn.addEventListener('click', (e) => {
                        log(this); // this指向obj这个对象
                        log(e.target); // 获取被点击的按钮
                        e.target.innerHTML = '点过';  //去操作按钮自身的属性
                        this.clickHandle(); // 调用外层对象的方法
                    });
                }
            };
            obj.init();
        })(document, window.console.log)
    </script>
    

    不要在最外层定义箭头函数,因为在函数内部操作this会很容易污染全局作用域。最起码在箭头函数外部包一层普通函数,将this控制在可见的范围内。

    2.5.2 不要滥用箭头函数

    // 普通的构造函数
    function Person( name){
        console.log(this);
        this.name =name;
    }
    
    var p1=new Person('小王'); // Person {name: "小王"}
    var p1=new Person('小李'); // Person {name: "小李"}
    
    
    // 箭头函数不能用作构造函数
    let Person = ( name) => {
        console.log(this);
        this.name =name;
    }
    
    let p1=new Person('小王'); // 报错:Person is not a constructor
    let p1=new Person('小李'); // 报错:Person is not a constructor
    

    Person构造函数的this,指向每次实例化的对象,若是用箭头函数改写构造函数,就会改变this的指向外层的this对象。所以箭头函数不能用作构造函数。除此之外箭头函数还有一些不能使用的情况,大家可以参考这篇文章:王仕军写的 什么时候你不能使用箭头函数?

    箭头函数最吸引人的地方是简洁。在有多层函数嵌套的情况下,箭头函数的简洁性并没有很大的提升,反而影响了函数的作用范围的识别度,这种情况不建议使用箭头函数。

    箭头函数有几个使用注意点。
    
    (1)箭头函数体内的this对象,就是`定义`时所在的对象,而不是`调用`时所在的对象。
    
    (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会报错。
    
    (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
    

    以上就是函数扩展的主要内容,其中尤为重要的是默认值参数和箭头函数。

    小结

    • 函数参数默认值:

      • 可以传任意类型
      • 不能用let或const再次声明
      • 一个函数只能有一个rest参数,只能是最后一个参数
    • 箭头函数:

      • 参数不传、只传一个,传多个,以及代码块为单行、多行的书写形式
      • 箭头函数中的this指向

    相关文章

      网友评论

          本文标题:ES6学习之- 函数的扩展

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