• ES6知识点整理之----函数扩展


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

    function log(x, y = 'World') {
      console.log(x, y);
    }
    
    log('Hello') // Hello World
    log('Hello', 'China') // Hello China
    log('Hello', '') // Hello

    参数变量是默认声明的,所以不能用letconst再次声明。

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

    参数默认值是惰性求值的。

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

    function foo({x, y = 5} = {}) {
      console.log(x, y);
    }
    
    foo({}) // undefined 5
    foo({x: 1}) // 1 5
    foo({x: 1, y: 2}) // 1 2
    foo() // undefined 5
    // 写法一
    function m1({x = 0, y = 0} = {}) {
      return [x, y];
    }
    
    // 写法二
    function m2({x, y} = { x: 0, y: 0 }) {
      return [x, y];
    }
    
    //上面两种写法都对函数的参数设定了默认值,区别是写法一函数参数的默认值是空对象,但是设置了对象解构赋值的默认值;写法二函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。

    3、参数默认值的位置

    如果非尾部的参数设置默认值,这个参数是没法省略的,需要显式输入undefined。

    4、函数的length属性

    指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。rest 参数也不会计入length属性。

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

    5、作用域

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

    如果参数的默认值是一个函数,该函数的作用域也遵守这个规则。

    参数的默认值不是在定义时执行,而是在运行时执行。

    可以将参数默认值设为undefined,表明这个参数是可以省略的。

    6、rest参数

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

    // arguments变量的写法
    function sortNumbers() {
      return Array.prototype.slice.call(arguments).sort();
    }
    
    // rest参数的写法
    const sortNumbers = (...numbers) => numbers.sort();

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

    函数的length属性,不包括 rest 参数。

    7、严格模式

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

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

    function foo() {}
    foo.name // "foo"

    Function构造函数返回的函数实例,name属性的值为anonymous

    (new Function).name // "anonymous"

    bind返回的函数,name属性值会加上bound前缀。

    function foo() {};
    foo.bind({}).name // "bound foo"
    
    (function(){}).bind({}).name // "bound "

    9、箭头函数

    ES6 允许使用“箭头”(=>)定义函数。

    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;
    };

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

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

    箭头函数有几个使用注意点。

    (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

    (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

    (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

    (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

    this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

    除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:argumentssupernew.target

    另外,由于箭头函数没有自己的this,所以当然也就不能用call()apply()bind()这些方法去改变this的指向。

    箭头函数内部,还可以再使用箭头函数。即嵌套箭头函数。

    10、双冒号运算符

    函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即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));

    11、尾调用

    指某个函数的最后一步是调用另一个函数。

    function f(x){
      return g(x);
    }

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

    function f(x) {
      if (x > 0) {
        return m(x)
      }
      return n(x);
    }

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

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

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

    尾调用优化:即只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。

    12、尾递归

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

    function factorial(n, total) {
      if (n === 1) return total;
      return factorial(n - 1, n * total);
    }
    
    factorial(5, 1) // 120

    尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。

    柯里化(currying),意思是将多参数的函数转换成单参数的形式。

    ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。

    尾递归优化的实现:采用“循环”换掉“递归”。

    蹦床函数(trampoline)可以将递归执行转为循环执行。

    function trampoline(f) {
      while (f && f instanceof Function) {
        f = f();
      }
      return f;
    }

    即不是函数里面调用函数,而是返回一个函数,然后执行该函数。

    蹦床函数并不是真正的尾递归优化,下面的实现才是。

    function tco(f) {
      var value;
      var active = false;
      var accumulated = [];
    
      return function accumulator() {
        accumulated.push(arguments);
        if (!active) {
          active = true;
          while (accumulated.length) {
            value = f.apply(this, accumulated.shift());
          }
          active = false;
          return value;
        }
      };
    }
    
    var sum = tco(function(x, y) {
      if (y > 0) {
        return sum(x + 1, y - 1)
      }
      else {
        return x
      }
    });
    
    sum(1, 100000)
    // 100001

    13、函数参数尾逗号

    ES2017 允许函数的最后一个参数有尾逗号

    function clownsEverywhere(
      param1,
      param2,
    ) { /* ... */ }
    
    clownsEverywhere(
      'foo',
      'bar',
    );
  • 相关阅读:
    leetcode
    vue初尝试--组件
    vue初尝试--项目结构
    vue初尝试--新建项目
    SQL取xml中节点值
    UI设计
    Asp.NET中Gridview CSS---Office2003风格
    Jquery+JSON+WebService使用总结
    ASP.NET 如何固定表格表头(fixed header)
    项目总结
  • 原文地址:https://www.cnblogs.com/adhehe/p/9645507.html
Copyright © 2020-2023  润新知