「前端每日一问(26)」箭头函数和普通函数有啥区别?

480 阅读4分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

本题难度:⭐ ⭐

答:

ES6 设计箭头函数主要有两个作用:

  • 让函数代码写起来更简洁
  • 解决对象内函数嵌套函数时的 this 指向问题

代码更简洁

const f = v => v
// 等同于
const f = function (v) {
  return v
}
const f = () => 5
// 等同于
const f = function () {
  return 5
}

const sum = (num1, num2) => num1 + num2
// 等同于
const sum = function (num1, num2) {
  return num1 + num2
}

改变 this 指向

对象里有函数嵌套函数的情况,使用 this 会指向 window。

比如下面这个例子,DOM 事件的回调函数封装在一个对象里面。

const handler = {
  id: '123456',
  init: function () {
    document.addEventListener(
      'click',
      function (event) {
        return this.doSomething(event.type)
      },
      false
    )
  },
  doSomething: function (type) {
    console.log('Handling ' + type + ' for ' + this.id)
  }
}

handler.init()

点击事件触发的时候,直接报错。

image.png

因为 window 上根本没有 doSomething 这个方法,这个方法是在 handler 对象中的。

改造成箭头函数就可以解决这个问题,改变 this 指向,让 this 指向当前函数上一层作用域中的 this,也就是 handler 这个对象,也就可以正常调用 doSomething 这个方法。

const handler = {
  id: '123456',
  init: function () {
    document.addEventListener(
      'click',
      event => this.doSomething(event.type), // 这么写就没问题
      false
    )
  },
  doSomething: function (type) {
    console.log('Handling ' + type + ' for ' + this.id)
  }
}

handler.init()

可以用 babelES5 来查看 this 指向改变的原理,其实就是用一个临时变量 _this 把箭头函数上一层作用域中的 this 存起来,用在箭头函数中:

image.png

其他需要注意的

this 指向不会改变

由于箭头函数没有自己的this指针,通过 callapplybind 方法调用一个函数时,只能传递参数不能绑定this,他们的第一个参数会被忽略。

const adder = {
  base: 1,

  add: function (a) {
    const f = v => v + this.base
    return f(a)
  },

  addThruCall: function (a) {
    const f = v => v + this.base
    const b = {
      base: 2
    }

    return f.call(b, a)
  }
}

console.log(adder.add(1)) // 输出 2
console.log(adder.addThruCall(1)) // 仍然输出 2

没有 arguments

在箭头函数里调用 arguments,直接报错。

const arr = () => {
  console.log(arguments)
}

console.log(arr(1, 2, 3)) // Uncaught ReferenceError: arguments is not defined

可以使用 剩余参数 来代替 arguments

const fn = (...args) => {
  console.log(args)
}

console.log(fn(1, 2, 3)) // [1, 2, 3]

不是构造函数

把箭头函数当作构造函数,直接报错。

const Fn = () => {}

const f = new Fn() // Uncaught TypeError: Fn is not a constructor

不能重复声明函数

普通函数可以重复声明,后面的会覆盖前面的

function print () {
  console.log(1)
}

function print () {
  console.log(2)
}

print() // 输出 2

箭头函数不行,直接报错

function print () {
  console.log(1)
}

const print = () => { // Module parse failed: Identifier 'print' has already been declared
  console.log(1)
}

不适合用箭头函数的场景

由于箭头函数使得 this 从“动态”变成“静态”,下面两个场合不应该使用箭头函数。

第一个场合是定义对象的方法,且该方法内部包括this

const cat = {
  lives: 9,
  jumps: () => {
    this.lives--
  }
}

上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。

第二个场合是需要动态this的时候,也不应使用箭头函数。

const button = document.getElementById('press')
button.addEventListener('click', () => {
  this.classList.toggle('on')
})

上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。

另外,如果函数体很复杂,有许多行,或者函数内部有大量的读写操作,不单纯是为了计算值,这时也不应该使用箭头函数,而是要使用普通函数,这样可以提高代码可读性。

参考资料:

ES6 - 箭头函数

结尾

如果我的文章对你有帮助,你的👍就是对我的最大支持^_^

我是阿林,输出洞见技术,再会!

上一篇:

「前端每日一问(25)」说一下函数形参、实参、剩余参数、默认参数、隐式参数

下一篇:

「前端每日一问(27)」JS 中执行上下文是什么?