zoukankan      html  css  js  c++  java
  • js-ES6学习笔记-函数的扩展

    1、ES6函数参数的默认值,直接写在参数定义的后面。参数变量是默认声明的,所以不能用let或const再次声明。

    function Point(x = 0, y = 0) {
      this.x = x;
      this.y = y;
    }
    
    var p = new Point();
    p // { x: 0, y: 0 }
    
    function foo(x = 5) {
      let x = 1; // error
      const x = 2; // error
    }

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

    3、指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。rest参数(可变参数,将会放入一个数组中,形如...args)也不会计入length属性。

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

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

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

    5、利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行)。

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

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

    6、ES6 引入 rest 参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。rest 参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

    7、扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。(rest参数常用与函数声明中,该运算符主要用于函数调用。)

    8、由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

    9、扩展运算符的应用:

    1. 合并数组
      // ES5
      [1, 2].concat(more)
      // ES6
      [1, 2, ...more]
      
      var arr1 = ['a', 'b'];
      var arr2 = ['c'];
      var arr3 = ['d', 'e'];
      
      // ES5的合并数组
      arr1.concat(arr2, arr3);
      // [ 'a', 'b', 'c', 'd', 'e' ]
      
      // ES6的合并数组
      [...arr1, ...arr2, ...arr3]
      // [ 'a', 'b', 'c', 'd', 'e' ]
    2. 与解构赋值结合
      const [first, ...rest] = [1, 2, 3, 4, 5];
      first // 1
      rest  // [2, 3, 4, 5]

      如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

    3. 函数的返回值
    4. 字符串扩,展运算符还可以将字符串转为真正的数组。
      [...'hello']
      // [ "h", "e", "l", "l", "o" ]

      上面的写法,有一个重要的好处,那就是能够正确识别32位的Unicode字符。

    5. 实现了Iterator接口的对象,任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。
      var nodeList = document.querySelectorAll('div');
      var array = [...nodeList];
    6. Map和Set结构,Generator函数

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

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

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

    箭头函数的一个用处是简化回调函数。

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

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

    11、ES7提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。虽然该语法还是ES7的一个提案,但是Babel转码器已经支持。

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

    12、尾调用(Tail Call)是函数式编程的一个重要概念,是指某个函数的最后一步是调用另一个函数。尾调用不一定出现在函数尾部,只要是最后一步操作即可,即return一个函数调用。

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

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

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

    注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。

    14、函数调用自身,称为递归。如果尾调用自身,就称为尾递归。递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。

    //非尾递归阶乘,复杂度 O(n) 
    function factorial(n) {
      if (n === 1) return 1;
      return n * factorial(n - 1);
    }
    
    factorial(5) // 120
    
    //尾递归阶乘,复杂度O(1)
    function factorial(n, total) {
      if (n === 1) return total;
      return factorial(n - 1, n * total);
    }
    
    factorial(5, 1) // 120
    
    //非尾递归fib数列
    function Fibonacci (n) {
      if ( n <= 1 ) {return 1};
    
      return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
    
    Fibonacci(10); // 89
    // Fibonacci(100)
    // Fibonacci(500)
    // 堆栈溢出了
    
    //尾递归fib数列
    function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
      if( n <= 1 ) {return ac2};
    
      return Fibonacci2 (n - 1, ac2, ac1 + ac2);
    }
    
    Fibonacci2(100) // 573147844013817200000
    Fibonacci2(1000) // 7.0330367711422765e+208
    Fibonacci2(10000) // Infinity

    15、尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。(使用柯里化或者ES6默认值可以保持原本的调用方式)

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

  • 相关阅读:
    poj3278 Catch That Cow
    poj2251 Dungeon Master
    poj1321 棋盘问题
    poj3083 Children of the Candy Cor
    jvm基础知识—垃圾回收机制
    jvm基础知识1
    java面试基础必备
    java soket通信总结 bio nio aio的区别和总结
    java scoket aIO 通信
    java scoket Blocking 阻塞IO socket通信四
  • 原文地址:https://www.cnblogs.com/zczhangcui/p/6418805.html
Copyright © 2011-2022 走看看