ES6学习笔记——函数的拓展

作者: island_0d48 | 来源:发表于2017-09-07 01:06 被阅读44次

    函数的拓展

    函数默认值

    到了ES6,函数终于有默认值了,在ES5中其实是可以用一些变通的方法来完成参数默认的,但是有一些缺点,比如说如果参数赋值了,但是对应的布尔值为false,则该赋值不起作用。ES6就解决了这个问题。

    // ES5
    function log(x, y) {
      y = y || 'World';
      console.log(x, y);
    }
    
    log('Hello') // Hello World
    log('Hello', 'China') // Hello China
    log('Hello', '') // Hello World 出现问题
    
    // ES6
    function log(x, y = 'World') {
      console.log(x, y);
    }
    
    log('Hello') // Hello World
    log('Hello', 'China') // Hello China
    log('Hello', '') // Hello 没毛病
    

    参数变量是默认声明的,所以不能用let或者const再次声明

    function foo(x = 5) {
      let x = 1; // error
      const x = 2; // error
    }
    

    使用参数默认值时,函数不能有同名参数

    // 不报错
    function foo(x, x, y) {
      // ...
    }
    
    // 报错
    function foo(x, x, y = 1) {
      // ...
    }
    // SyntaxError: Duplicate parameter name not allowed in this context
    

    参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的

    let x = 99;
    function foo(p = x + 1) {
      console.log(p);
    }
    
    foo() // 100
    
    x = 100;
    foo() // 101
    

    每次调用都重新计算

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

    参数默认值可以和解构赋值的默认值,结合起来使用。
    更骚气的是如果参数时一个对象,对象里面做了默认值,比如{x, y=5},这个时候如果啥也不传就会报错,这个时候可以通过提供函数参数的默认值,避免报错。

    function foo({x, y = 5} = {}) {
      console.log(x, y);
    }
    
    foo() // undefined 5
    
    function fetch(url, { body = '', method = 'GET', headers = {} }) {
      console.log(method);
    }
    
    fetch('http://example.com', {})
    // "GET"
    
    fetch('http://example.com')
    
    function fetch(url, { method = 'GET' } = {}) {
      console.log(method);
    }
    
    fetch('http://example.com')
    // "GET"
    

    出现了双重默认值。

    参数默认值的位置

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

    // 例一
    function f(x = 1, y) {
      return [x, y];
    }
    
    f() // [1, undefined]
    f(2) // [2, undefined])
    f(, 1) // 报错
    f(undefined, 1) // [1, 1]
    
    // 例二
    function f(x, y = 5, z) {
      return [x, y, z];
    }
    
    f() // [undefined, 5, undefined]
    f(1) // [1, 5, undefined]
    f(1, ,2) // 报错
    f(1, undefined, 2) // [1, 5, 2]
    

    上述情况都不能省略参数,只能设置undefined来触发等于默认值。但是null没有这个效果。

    函数的长度(length)

    函数的属性length返回函数没有指定默认值的参数的个数,如果指定了默认参数,那么将不计入length,length永远返回没有默认参数的传入参数个数。

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

    有趣的是这个length的计数是根据默认参数的位置来计算的,果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

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

    作用域

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

    var x = 1;
    
    function f(x, y = x) {
      console.log(y);
    }
    
    f(2) // 2
    
    let x = 1;
    
    function f(y = x) {
      let x = 2;
      console.log(y);
    }
    
    f() // 1
    

    上面两种情况,第一种就是指向第一个x,外面全局的x不会影响y的赋值。但是第二种情况因为x本身没有定义,所以会指向外部全局的x,但是在内部x的操作,并不会影响到y的值。

    应用

    利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出错误。

    function throwIfMissing() {
      throw new Error('Missing parameter');
    }
    
    function foo(mustBeProvided = throwIfMissing()) {
      return mustBeProvided;
    }
    
    foo()
    // Error: Missing parameter
    

    rest参数

    ES5中函数传参可以用arguments,在ES6加入了rest参数来使得传参更加地优雅,用法为...变量名。

    function add(...values) {
      let sum = 0;
    
      for (var val of values) {
        sum += val;
      }
    
      return sum;
    }
    
    add(2, 5, 3) // 10
    

    注意,rest参数必须是最后一个参数,否则会报错

    严格模式

    ES5开始函数内部可以设定为严格模式,但是ES6引入默认值、解构赋值、扩展运算符之后,函数内部就不可以使用严格模式了,否则就会报错。

    因为函数内部的严格模式,同样适用于函数体和函数参数,但是函数执行的时候,先执行函数参数,然后执行函数体,这就不合理了,只有从函数体之中,才能知道参数是否应该以严格模式执行,但是参数应该先初始化。

    当然了,你可以吧“use strict”放在外面或者用一个立即执行的函数包住函数。

    'use strict';
    
    function doSomething(a, b = a) {
      // code
    }
    
    const doSomething = (function () {
      'use strict';
      return function(value = 42) {
        return value;
      };
    }());
    

    name属性

    函数的name属性,直接返回函数的名字,有很多种情况,比如匿名函数还有bind的函数等,详见代码。

    var f = function () {};
    
    // ES5
    f.name // ""
    
    // ES6
    f.name // "f"
    
    const bar = function baz() {};
    
    // ES5
    bar.name // "baz"
    
    // ES6
    bar.name // "baz"
    
    (new Function).name // "anonymous"
    
    function foo() {};
    foo.bind({}).name // "bound foo"
    
    (function(){}).bind({}).name // "bound "
    

    箭头函数

    ES6的箭头表达式是非常有用的一个东西。哇,真的方便。

    如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

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

    如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

    var sum = (num1, num2) => { return num1 + num2; }
    由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

    // 报错
    let getTempItem = id => { id: id, name: "Temp" };
    
    // 不报错
    let getTempItem = id => ({ id: id, name: "Temp" });
    

    如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。

    let fn = () => void doesNotReturn();
    

    箭头函数可以与变量解构结合使用。

    const full = ({ first, last }) => first + ' ' + last;
    
    // 等同于
    function full(person) {
      return person.first + ' ' + person.last;
    }
    

    箭头函数使得表达更加简洁。
    箭头函数的一个用处是简化回调函数。

    // 正常函数写法
    [1,2,3].map(function (x) {
      return x * x;
    });
    
    // 箭头函数写法
    [1,2,3].map(x => x * x);
    另一个例子是
    
    // 正常函数写法
    var result = values.sort(function (a, b) {
      return a - b;
    });
    
    // 箭头函数写法
    var result = values.sort((a, b) => a - b);
    

    下面是 rest 参数与箭头函数结合的例子。

    const numbers = (...nums) => nums;
    
    numbers(1, 2, 3, 4, 5)
    // [1,2,3,4,5]
    
    const headAndTail = (head, ...tail) => [head, tail];
    
    headAndTail(1, 2, 3, 4, 5)
    // [1,[2,3,4,5]]
    

    注意点

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

    第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的

    绑定this

    箭头函数可以绑定this对象,这就可以减少bind显式的调用。

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

    如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

    由于双冒号运算符返回的还是原对象,因此可以采用链式写法。

    foo::bar;
    // 等同于
    bar.bind(foo);
    
    foo::bar(...arguments);
    // 等同于
    bar.apply(foo, arguments);
    
    const hasOwnProperty = Object.prototype.hasOwnProperty;
    function hasOwn(obj, key) {
      return obj::hasOwnProperty(key);
    }
    
    var method = obj::obj.foo;
    // 等同于
    var method = ::obj.foo;
    
    let log = ::console.log;
    // 等同于
    var log = console.log.bind(console);
    
    // 例一
    import { map, takeWhile, forEach } from "iterlib";
    
    getPlayers()
    ::map(x => x.character())
    ::takeWhile(x => x.strength > 100)
    ::forEach(x => console.log(x));
    
    // 例二
    let { find, html } = jake;
    
    document.querySelectorAll("div.myClass")
    ::find("p")
    ::html("hahaha");
    

    尾逗号

    ES6支持参数的最后一个后面可以接一个逗号

    function clownsEverywhere(
      param1,
      param2,
    ) { /* ... */ }
    
    clownsEverywhere(
      'foo',
      'bar
    

    相关文章

      网友评论

        本文标题:ES6学习笔记——函数的拓展

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