美文网首页前端开发那些事儿程序员
什么是ES6箭头函数?与普通函数主要区别在哪里?到底该不该使用箭

什么是ES6箭头函数?与普通函数主要区别在哪里?到底该不该使用箭

作者: 十八岁的天空_b2de | 来源:发表于2020-07-27 18:00 被阅读0次

    箭头函数是匿名函数,ES5匿名函数的语法糖;但又增加了ES5所没有的一些优点,接下来我们一起来看一看箭头函数

    // ES5
    vat tt = function tt() {
      return 55 + 99;
    }
    
    // ES6
    vat tt = () => 55 + 99
    // 是不是一对比,写法的差异就看出来了
    

    ES6增加了箭头函数:

    let func = value => value;
    // 相当于
    let func = function () {
      return value;
    }
    // 如果需要给函数传入多个参数:
    let func = (value, num) => value * num;
    // 如果函数的代码块需要多条语句:
    let func = (value, num) => {
      return value * num;
    }
    // 如果需要直接返回一个对象:
    let func = (value, num) => ({total: value * num});
    // 与变量解构结合
    let func = ({value, num}) => ({total: value * num})
    // 使用
    var result = func({
      value: 10,
      num: 10
    })
    console.log(result);  // {total: 100}
    

    以上是箭头函数的用法,了解了箭头函数的用法之后呢,我们一起来进一步的看一看箭头函数,到底和我们的普通函数有什么区别,好处在哪?

    1.没有this

    箭头函数没有this,所以需要通过查找作用域链来确定this的值。
    这就意味着如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this。
    模拟一个实际开发中的例子:
    我们的需求是点击一个按钮,改变该按钮的背景色。
    为了方便开发,我们抽离一个Button组件,当需要使用的时候直接:传入元素id值即可绑定该元素点击时改变背景色的事件

    new Button('button');
    // HTML代码如下:
    <button id="button">点击变色</button>
    // javascript代码:
    function Button(id) {
      this.element = document.querySelector('#' + id);
      this.bindEvent();
    }
    
    Button.prototype.bindEvent = function() {
      this.element.addEventListener("click", this.setBgColor, false);
    }
    
    Button.prototype.setBgColor = function() {
      this.element.style.backgroundColor = '#abcdef';
    }
    
    vat button = new Button('button');
    

    看着好像没有问题,结果却是报错 Uncaught TypeError: Cannot read property 'style' of undefined
    这是因为当使用addEventListener() 为一个元素注册事件的时候,事件函数里的this值是该元素的引用。
    所以如果我们在setBgColor中console.log(this), this指向的是按钮元素,那this.element就是undefined, 报错自然就理所当然了。
    也许你会问,既然this都指向了按钮元素,那我们直接修改setBgColor函数为:

    Button.prototype.setBgColor = function() {
      this.style.backgroundColor = '#abcdef';
    }
    

    不就可以解决这个问题了?
    确实可以这样做,但是在实际开发中,我们可能会在setBgColor中还调用其他的函数,比如写成这样:

     Button.prototype.setBgColor = function() {
      this.setElementColor();
      this.setOtherElementColor();
    }
    

    所以我们还是希望setBgColor中的this是指向实例对象的,这样就可以调用其他的函数。
    利用ES5,我们一般会这样做:

    Button.prototype.bindEvent = function() {
      this.element.addEventListener("click", this.setBgColor.bind(this), false);
    }
    

    为避免addEventListener的影响,使用bind强制绑定setBgColor()的this为实例对象
    使用ES6,我们可以更好的解决这个问题:

    Button.prototype.bindEvent = function() {
      this.element.addEventListener("click", event => this.setBgColor(event), false);
    }
    

    由于箭头函数没有this,所以会向外层查找this的值,即bindEvent中的this,此时this指向实例对象,所以可以正确的调用this.setBgColor方法,而this.setBgColor中的this也会正确指向实例对象。
    在这里再额外提一点,就是注意bindEvent和setBgColor在这里使用的是普通函数的形式,而非箭头函数,如果我们改成箭头函数,会导致函数里的this指向window对象(非严格模式下)。
    最后,因为箭头函数没有this,所以也不能用call()、apply()、bind()这些方法改变this的指向,可以看一个例子:

    var value = 1;
    var result = (() => this.value).bind({value: 2})();
    console.log(result); // 1
    
    2.没有arguments

    箭头函数没有自己的arguments对象,这不一定是件坏事,因为箭头函数可以访问外围函数的arguments对象:

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

    那如果我们就是要访问箭头函数的参数呢?
    你可以通过命名参数或者rest参数的形式访问参数:

    let nums = (...nums) => nums;
    
    3.不能通过new关键字调用

    Javascript函数有两个内部方法:[[Call]]和[[Construct]]。
    当通过new调用函数时,执行[[Construct]]方法,创建一个实例对象,然后再执行函数体,将this绑定到实例上。
    当直接调用的时候,执行[[Call]]方法,直接执行函数体。
    箭头函数并没有[[Construct]]方法,不能被用作构造函数,如果通过new的方式调用,会报错。

    var Foo = () => {};
    var foo = new Foo(); // TypeError: Foo is not a constructor
    
    4.没有原型

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

    5.没有super

    连原型都没有,自然也不能通过super来访问原型的属性,所以箭头函数也没有super的,不过跟this、arguments一样,这些值由外围最近一层非箭头函数决定。
    在出现了箭头函数之后是不是就可以舍弃原来的普通函数了呢?答案一定是否定的,一些情况下我们最好不要去进行箭头函数的操作,那都有哪些情况呢?

    (1) 在对象上定义函数

    先来看下面这段代码

    var obj = {
      array: [1, 2, 3],
      sum: () => {
        console.log(this === window); // => true
        return this.array.reduce((result, item) => result + item);
      }
    }
    obj.sum();
    // Throws "TypeError: Cannot read property 'reduce' of undefined"
    

    sum方法定义在obj对象上,当调用的时候我们发现抛出了一个TypeError,因为函数中的this是window对象,所以this.array也是undefined。原因也很简单,相信只要了解es6 箭头函数的都知道
    箭头函数没有它自己的this值,箭头函数内的this值继承自外围作用域
    解决方法也很简单,就是不用呗。这里可以用es6里函数表达式的简洁语法,在这种情况下,this值就取决于函数的调用方式了。

    var obj = {
      array: [1, 2, 3],
      sum() {
        console.log(this === obj); // => true
        return this.array.reduce((result, item) => result + item);
      }
    }
    obj.sum(); // => 6
    

    通过object.method()语法调用的方法使用非箭头函数定义,这些函数需要从调用者的作用域中获取一个有意思的this值。

    (2)在原型上定义函数

    在对象原型上定义函数也是遵循着一样的规则

    function Person(pName) {
      this.pName = pName;
    }
    Person.prototype.sayName = () => {
      console.log(this === window); // => true
      return this.pName;
    }
    var person = new Person('zhangSan');
    person.sayName(); // => undefined
    
    // 使用function函数表达式
    function Person(pName) {
      this.pName = pName;
    }
    Person.prototype.sayName = function() {
      console.log(this === person); // => true
      return this.pName;
    }
    var person = new Person('zhangSan');
    person.sayName(); // => zhangSan
    
    (3)动态上下文中的回调函数

    this是js中非常强大的特点,他让函数可以根据其调用方式动态的改变上下文,然后箭头函数直接在声明时就绑定了this对象,所以不再是动态的。
    在客户端,在dom元素上绑定事件监听函数是非常普遍的行为,在dom事件被触发时,回调函数中的this指向该dom,可当我们使用箭头函数时:

    var button = document.getElementById('myButton');
    button.addEventListener('click', () => {
      console.log(this === window); // => true
      this.innerHTML = 'clicked button';
    })
    

    因为这个回调的箭头函数是在全局上下文中被定义的,所以他的this是window。所以当this是由目标对象决定时,我们应该使用函数表达式:

    var button = document.getElementById('myButton');
    button.addEventListener('click', function() {
      console.log(this === button); // => true
      this.innerHTML = 'clicked button';
    })
    
    (4)构造函数中

    在构造函数中,this指向新创建的对象实例
    this instanceOf MyFunction === true
    需要注意的是,构造函数不能使用箭头函数,如果这样做会抛出异常

    var Person = (name) => {
      this.name = name;
    }
    var person = new Person('zhangSan');
    // Uncaught TypeError: Person is not a constructor
    

    理论上来说也是不能这么做的,因为箭头函数在创建时this对象就绑定了,更不会指向对象实例。

    (5)太简短的函数

    箭头函数可以让语句写的非常的简洁,但是一个真实的项目,一般由多个开发者共同协作完成,就算由单人完成,后期也并不一定是同一个人维护,箭头函数有时候并不会让人很好的理解,比如

    let multiply = (a, b) => b === undefined ? b => a * b : a * b;
    let double = multiply(2);
    double(3); // => 6
    multiply(2,3); // => 6
    

    这个函数的作用就是当只有一个参数a时,返回接受一个参数b返回a * b的函数,接受两个参数时直接返回乘积,这个函数可以很好的工作并且看起来很简洁,但是从第一眼看去并不是很好理解。为了让这个函数更好的让人理解,我们可以为这个箭头函数加一对花括号,并加上return语句买或者直接使用函数表达式:

    function multiply(a, b) {
      if (b === undefined) {
        return function (b) {
          return a * b;
        }
      }
      return a * b;
    }
    let double = multiplu(2);
    double(3); // => 6;
    multiply(2, 3); // => 6
    

    总结
    最后,关于箭头函数,引用MDN的介绍就是:箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super。
    毫无疑问,箭头函数带来了很多便利。恰当的使用箭头函数可以让我们避免使用早起的.bind()函数或者需要固定上下文的地方并且让代码更加简洁。
    箭头函数也有一些不便利的地方。我们在需要动态上下文的地方不能使用箭头函数:定义需要动态上下文的函数,构造函数,需要this对象作为目标的回调函数以及用箭头函数难以理解的语句。在其他情况下,请尽情使用箭头函数。

    相关文章

      网友评论

        本文标题:什么是ES6箭头函数?与普通函数主要区别在哪里?到底该不该使用箭

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