es6之函数扩展(箭头函数)

500 阅读5分钟

1.函数与解构赋值的默认值结合使用

function m1({x = 0, y = 0} = {}) { 
   return [x, y];
}
function m2({x, y} = { x: 0, y: 0 }) {      //参数的默认值是一个有具体属性的对象
   return [x, y];
}
  // x 有值,y 无值的情况
  m1({x: 3}) // [3, 0]
  m2({x: 3}) // [3, undefined]

  ps:函数的length属性:返回没有指定默认值的参数个数,如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。

2.rest参数(...变量名)别名:剩余运算符

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

function sumRest (...m) {
    var total = 0; 
    for(var i of m){
        total += i;
    }
    return total;
}
console.log(sumRest(1,2,3));    //6      传递给 sumRest 函数的一组参数值,被整合成了数组 m。

使用arguments参数:
function sumArgu () {
     var result = 0;
     for (var i = 0; i < arguments.length; i++) {
        result += arguments[i];
    }
    return result
}
console.log(sumArgu(1,2,3));//6

注意:rest参数可理解为剩余的参数,所以必须在最后一位定义,后面不能有别的参数,如果定义在中间会报错。

  var array = [1,2,3,4,5,6];
  var [a,b,...c] = array;

3.严格模式

  (只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式)

如何避免:1.设定全局性的严格模式。2.把函数包在一个无参数的立即执行函数里面。

4.箭头函数(重点)

  普通函数和箭头函数的区别:

    (1)没有this

             箭头函数没有this,需要查找作用域链确定this。则如果箭头函数被非箭头函数包含,              this 绑定的就是最近一层非箭头函数的 this。

            例子:

         <button id="button">点击变色</button>

         function Button(id) {
           this.element = document.querySelector("#" + id);
           this.bindEvent();
         }

         Button.prototype.bindEvent = function() {   
           this.element.addEventListener("click", this.setBgColor, false);  // 注册事件
           // this 值是该元素的引用this = Button {element: button#button}
         };

         Button.prototype.setBgColor = function() {  
           //this 指向的是button 本身  <button id="button">点击变色</button>
           this.element.style.backgroundColor = '#1abc9c'  //所以这里会报错
           this.style.backgroundColor = '#1abc9c'   // 直接修改
         };
         e6的方法:
         Button.prototype.bindEvent = function() {           this.element.addEventListener("click", event => this.setBgColor(event), false);
         };
         var button = new Button("button");

    因为箭头函数没有 this,所以也不能用 call()、apply()、bind() 这些方法改变 this 的指向

   (2)没有arguments

        arguments是传递给函数的参数的类数组对象

       例子:

   function func1(a, b, c) {
       console.log(arguments[0]);    // expected output: 1

       console.log(arguments[1]);    // expected output: 2

       console.log(arguments[2])    // expected output: 3
   }
   func1(1, 2, 3);

      但是,箭头函数可以访问外围函数的 arguments 对象

     例子:

   function constant() {
       return () => arguments[0]
   }
   var result = constant(1);
   console.log(result()); // 1

     如何访问箭头函数的参数?

     通过命名参数或者 rest 参数的形式访问参数。

      例子:

    let nums = [1,2,3]
    let num = (...nums) =>{console.log(nums)}           // [1,2,3]
    num(1,2,3)

    (3)不能通过new关键字调用

      var Foo = () => {};
      var foo = new Foo();                  // TypeError: Foo is not a constructor
      //箭头函数并没有 [[Construct]] 方法,不能被用作构造函数,如果通过 new 的方式调用,会报错

           因为JavaScript 函数有两个内部方法:[[Call]] 和 [[Construct]]。

           当通过 new 调用函数时,执行 [[Construct]] 方法,创建一个实例对象,然后再执行函             数体,将 this 绑定到实例上。

           执行new这个操作后的内部步骤:

            1.创建一个空对象,做为将要返回的对象实例。

            2.将这个空对象的原型,指向构造函数的 prototype 属性

            3.将这个空对象赋值给函数内部的 this 关键字

            4.开始执行构造函数内部的代码

            如果不用new关键字调用的函数,执行的是[[call]]方法,直接执行代码中的函数体。 

   (4)没有new.target

   (5)没有原型

       由于不能使用 new 调用箭头函数,所以也没有构建原型的需求,于是箭头函数也不存在           prototype 这个属性。   

   (6)没有super

    补充:箭头函数不适用的场景:

      1.定义对象的方法,且该方法内部包括this

     const cat = {
       lives: 9,
       jumps: () => {
         this.lives--;
       }
     }
   //对象不构成单独的作用域,调用cat.jumps()时,this指向全局,如果是普通函数,this指向cat

      2.需要动态this

5.尾调优化(只保留内层函数的调用帧)

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

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();
  // 如果g()不是尾部调用,那么f()需要保存m,n变量,g()调用的位置等信息,但是因为此处是尾部调用,
     则只保留g(3)的调用帧

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

6.尾递归(函数调用自身)

正常递归:(时间复杂度o(n),保存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

ES6 中只要使用尾递归,就不会发生栈溢出(或者层层递归造成的超时),相对节省内存。

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

此处有个函数柯里化,后面遇到,详细看

7.函数参数的尾逗号

    允许函数的最后一个参数有尾逗号(在此之前函数的定义和调用,都不允许最后一个参数出现逗号)

8.function.prototype.toString()

    要求返回一模一样的原始代码(以前返回的代码会自动省略注释和空格)

9.catch命令参数省略

try {
  // ...
} catch {             // 原来是catch(err)  现在可以省略这个参数

}