◆

JavaScript 箭头函数用法详解

2023-02-02 01:370

概述

箭头函数是 ES6 提供的函数简写语法。它不只是省几个字符,最重要的区别是它没有自己的 this,而是沿作用域链向外查找。这一条决定了它适合和不适合的场景。

基本写法:

// 完整形式
const add = (a, b) => {
  return a + b;
};

// 单表达式可以省略大括号和 return(隐式返回)
const add2 = (a, b) => a + b;

// 单参数可以省略括号
const double = n => n * 2;

// 无参数必须写空括号
const now = () => Date.now();

返回对象字面量时必须用括号包起来,否则大括号会被解析成函数体:

const makeUser = name => ({ name, id: 1 }); // 正确
// const bad = name => { name, id: 1 };     // 返回 undefined,且 name, id 被当语句

this 的差异

普通函数的 this 在调用时确定,取决于怎么调用;箭头函数的 this 在定义时确定,等于外层作用域的 this,而且无法通过 call、apply、bind 改变。

最典型的场景是回调里的 this 丢失:

const timer = {
  seconds: 0,
  // 普通函数:setTimeout 回调里的 this 是 undefined(严格模式)或 window
  brokenStart() {
    setInterval(function () {
      this.seconds++; // this 不是 timer
      console.log(this.seconds); // NaN
    }, 1000);
  },
  // 箭头函数:this 沿用 brokenStart 的 this,也就是 timer
  start() {
    setInterval(() => {
      this.seconds++;
      console.log(this.seconds); // 1, 2, 3...
    }, 1000);
  },
};

ES6 之前解决这个问题得在外面存一份 const self = this,或者给回调 .bind(this)。箭头函数把这个模式内建到了语法里。

反过来,需要动态 this 的地方绝对不能用箭头函数。DOM 事件处理器就是典型:

const btn = document.querySelector('button');

// 正确:this 指向触发事件的元素
btn.addEventListener('click', function () {
  this.classList.toggle('active');
});

// 错误:this 指向定义时的外层作用域(模块顶层是 undefined)
btn.addEventListener('click', () => {
  this.classList.toggle('active'); // TypeError
});

对象方法也一样。写成箭头函数,this 会指向定义对象字面量时的外层 this(不是这个对象),因为对象字面量本身不产生作用域:

const obj = {
  name: 'wfly',
  bad: () => console.log(this.name), // undefined
  good() {
    console.log(this.name); // 'wfly'
  },
};

没有 arguments

箭头函数不绑定 arguments,访问它拿到的是外层函数的 arguments,在顶层则直接 ReferenceError。要收集不定参数用 rest 参数:

const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
console.log(sum(1, 2, 3)); // 6

rest 参数比 arguments 更好用:它是真数组,可以直接 map、filter,而 arguments 是类数组对象,得先 Array.from 转换。

不能当构造函数

箭头函数没有 [[Construct]],也没有 prototype 属性:

const Foo = () => {};
// new Foo();               // TypeError: Foo is not a constructor
console.log(Foo.prototype); // undefined

同理它也不能作为生成器函数,没有 function* 的箭头形式,函数体里不能用 yield。

没有 super 和 new.target

在类的方法里用箭头函数,super 会沿用外层方法的 super 绑定,这通常是我们想要的。但如果需要 new.target 判断是否被 new 调用,箭头函数里拿到的也是外层的值。

关于「更短的写法」的取舍

隐式返回很方便,但滥用会写出不好读的代码。比如链式条件:

// 可读性差
const cls = active => (active ? (disabled ? 'a-d' : 'a') : 'n');

// 好一些
const cls = active => {
  if (!active) return 'n';
  return disabled ? 'a-d' : 'a';
};

另一个容易犯的错是在 map 里用带大括号的箭头函数但忘了写 return:

const ids = list.map(item => {
  item.id; // 忘了 return,结果全是 undefined
});

这类错误 ESLint 的 array-callback-return 规则能查出来,建议开着。

该用哪个的判断标准

用箭头函数:回调函数、需要词法 this 的场景(定时器、Promise.then、数组方法)、纯函数式的短工具函数、class 里需要绑定实例的处理器字段。

用普通函数:对象的方法(用方法简写 method() {})、需要 this 指向调用者的 DOM 事件处理器、构造函数、原型方法、需要 arguments 的兼容老代码、生成器函数。

class 里的处理器字段写成箭头函数还有个隐含代价:它是实例字段,每个实例存一份函数对象,不像原型方法那样共享。列表里渲染成千上万个组件实例时这部分内存不可忽略,权衡的做法是在构造函数里 bind 一次,或者把事件委托到父元素上。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEJavaScript 箭头函数用法详解加载中