箭头函数 vs 普通函数:区别与使用场景

85 阅读2分钟

JavaScript 中的函数是一等公民,可以作为变量传递、作为参数传入其他函数,或者作为返回值。ES6 引入的箭头函数为我们提供了一种更简洁的函数语法,但它与传统的函数声明有着本质的区别。

语法差异

普通函数

// 函数声明
function add(a, b) {
  return a + b;
}

// 函数表达式
const multiply = function(a, b) {
  return a * b;
};

箭头函数

// 基本语法
const add = (a, b) => {
  return a + b;
};

// 简化返回语句
const add = (a, b) => a + b;

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

// 无参数需要空括号
const getRandomNumber = () => Math.random();

核心区别

1. this 绑定机制

普通函数this 值取决于函数如何被调用,可以通过 callapplybind 方法改变。

const user = {
  name: '张三',
  greet: function() {
    console.log(`你好,我是 ${this.name}`);
  }
};

user.greet(); // 输出: 你好,我是 张三

const greetFunc = user.greet;
greetFunc(); // 输出: 你好,我是 undefined (在非严格模式下,this 指向全局对象)

箭头函数:没有自己的 this,它会继承定义时所在上下文的 this 值,且无法通过 callapplybind 改变。

const user = {
  name: '张三',
  greet: () => {
    console.log(`你好,我是 ${this.name}`);
  }
};

user.greet(); // 输出: 你好,我是 undefined (this 指向定义时的上下文,通常是全局对象)

const user2 = {
  name: '张三',
  greetRegular: function() {
    // 这里的 this 指向 user2
    const greetArrow = () => {
      console.log(`你好,我是 ${this.name}`);
    };
    greetArrow();
  }
};

user2.greetRegular(); // 输出: 你好,我是 张三

2. 构造函数

普通函数:可以作为构造函数使用,使用 new 关键字创建实例。

function Person(name) {
  this.name = name;
}

const person = new Person('张三');
console.log(person.name); // 输出: 张三

箭头函数:不能作为构造函数使用,没有 prototype 属性。

const Person = (name) => {
  this.name = name;
};

const person = new Person('张三'); // TypeError: Person is not a constructor

3. arguments 对象

普通函数:有自己的 arguments 对象,包含传递给函数的所有参数。

function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

console.log(sum(1, 2, 3, 4)); // 输出: 10

箭头函数:没有自己的 arguments 对象,但可以使用剩余参数(rest parameters)。

const sum = (...args) => {
  return args.reduce((total, current) => total + current, 0);
};

console.log(sum(1, 2, 3, 4)); // 输出: 10

4. 方法定义

普通函数:适合作为对象方法,特别是需要访问对象属性时。

const calculator = {
  value: 0,
  add(n) {
    this.value += n;
    return this.value;
  }
};

console.log(calculator.add(5)); // 输出: 5

箭头函数:不适合作为对象方法,因为 this 不会指向对象本身。

const calculator = {
  value: 0,
  add: (n) => {
    this.value += n; // this 不指向 calculator
    return this.value;
  }
};

console.log(calculator.add(5)); // 输出: NaN,因为 this.value 是 undefined

5. 生成器函数

普通函数:可以是生成器函数。

function* generateSequence() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = generateSequence();
console.log(generator.next().value); // 输出: 1

箭头函数:不能是生成器函数。

const generateSequence = *() => { // 语法错误
  yield 1;
  yield 2;
  yield 3;
};

使用场景建议

适合使用箭头函数的场景

  1. 简短的回调函数:特别是在数组方法中。
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
console.log(doubled); // 输出: [2, 4, 6, 8, 10]
  1. 需要保留外部 this 上下文的回调:避免使用 var self = thisbind 方法。
function Counter() {
  this.count = 0;
  
  // 使用箭头函数保留 this 上下文
  setInterval(() => {
    this.count++;
    console.log(this.count);
  }, 1000);
}

const counter = new Counter(); // 每秒输出递增的数字
  1. 链式方法调用:使代码更简洁。
const result = [1, 2, 3, 4, 5]
  .filter(n => n % 2 === 0)
  .map(n => n * 2)
  .reduce((sum, n) => sum + n, 0);

console.log(result); // 输出: 12
  1. 简单的工具函数:不需要 this 上下文的纯函数。
const isEven = num => num % 2 === 0;
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);

适合使用普通函数的场景

  1. 对象方法:需要访问对象本身的属性。
const person = {
  name: '张三',
  sayHello() {
    console.log(`你好,我是 ${this.name}`);
  }
};
  1. 构造函数:创建对象实例。
function User(name, age) {
  this.name = name;
  this.age = age;
}

User.prototype.getInfo = function() {
  return `${this.name}, ${this.age}岁`;
};
  1. 需要动态 this 绑定的函数:根据调用方式改变 this 指向。
function greet() {
  console.log(`你好,${this.name}`);
}

const person1 = { name: '张三' };
const person2 = { name: '李四' };

greet.call(person1); // 输出: 你好,张三
greet.call(person2); // 输出: 你好,李四
  1. 事件处理函数:需要访问事件目标。
document.getElementById('button').addEventListener('click', function(event) {
  console.log(this); // 指向按钮元素
  console.log(event.target); // 同样指向按钮元素
});
  1. 生成器函数:需要使用 yield 关键字。
function* paginate(items, pageSize) {
  let page = 0;
  while (page * pageSize < items.length) {
    yield items.slice(page * pageSize, (page + 1) * pageSize);
    page++;
  }
}

性能考虑

从性能角度看,箭头函数和普通函数之间没有显著差异。选择使用哪种函数类型应该基于语法、this 绑定和其他功能需求,而不是性能考虑。

总结

箭头函数和普通函数各有优缺点,选择使用哪种取决于具体的使用场景:

  • 如果需要 this 指向定义函数的上下文,使用箭头函数
  • 如果需要函数作为构造函数、方法或需要动态 this 绑定,使用普通函数
  • 对于简短的回调函数和不需要 this 的工具函数,箭头函数通常是更好的选择