箭头函数与普通函数有哪些区别

4 阅读5分钟

1. 核心区别概览

特性普通函数 (Function)箭头函数 (Arrow Function)
this绑定动态绑定,取决于调用方式词法绑定,继承外层作用域的this
arguments对象
构造函数可以,可使用new不可以,使用new会报错
prototype属性
yield关键字可以在生成器函数中使用不能使用(除非外层是普通函数)
语法有多种形式更简洁,适合回调函数
重复命名参数严格模式下不允许不允许
super可以在类方法中使用可以在类方法中使用(需注意this)

2. 详细对比分析

2.1 this 绑定的区别(最重要!)

// 普通函数的 this 是动态绑定的
const obj = {
  name: 'Alice',
  regularFunc: function() {
    console.log('Regular:', this.name);
  },
  arrowFunc: () => {
    console.log('Arrow:', this.name);
  }
};

obj.regularFunc(); // 'Regular: Alice' - this 指向 obj
obj.arrowFunc();   // 'Arrow: undefined' - this 指向外层作用域(这里可能是window或global)

// 更清晰的例子
const person = {
  name: 'Bob',
  hobbies: ['reading', 'coding'],
  
  showHobbiesRegular: function() {
    this.hobbies.forEach(function(hobby) {
      // 这里的 this 指向全局对象,不是 person
      console.log(`${this.name} likes ${hobby}`);
      // 输出: undefined likes reading
    });
  },
  
  showHobbiesArrow: function() {
    this.hobbies.forEach((hobby) => {
      // 箭头函数继承外层函数的 this
      console.log(`${this.name} likes ${hobby}`);
      // 输出: Bob likes reading, Bob likes coding
    });
  }
};

person.showHobbiesRegular();
person.showHobbiesArrow();

// 事件监听中的 this
const button = document.querySelector('button');

button.addEventListener('click', function() {
  console.log(this); // <button> - this 指向按钮元素
  setTimeout(function() {
    console.log(this); // Window - this 指向全局对象
  }, 100);
});

button.addEventListener('click', function() {
  console.log(this); // <button> - this 指向按钮元素
  setTimeout(() => {
    console.log(this); // <button> - 箭头函数继承外层 this
  }, 100);
});

2.2 构造函数与 new

// 普通函数可以作为构造函数
function Person(name) {
  this.name = name;
}
Person.prototype.greet = function() {
  return `Hello, I'm ${this.name}`;
};

const alice = new Person('Alice');
console.log(alice.greet()); // "Hello, I'm Alice"

// 箭头函数不能作为构造函数
const Animal = (name) => {
  this.name = name; // 错误!
};

// const dog = new Animal('Dog'); // TypeError: Animal is not a constructor

// 箭头函数没有 prototype 属性
console.log(Person.prototype); // {greet: ƒ, constructor: ƒ}
console.log(Animal.prototype); // undefined

2.3 arguments 对象

// 普通函数有 arguments 对象
function regularSum() {
  console.log(arguments); // [1, 2, 3, 4, 5]
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}
console.log(regularSum(1, 2, 3, 4, 5)); // 15

// 箭头函数没有 arguments 对象
const arrowSum = () => {
  // console.log(arguments); // ReferenceError: arguments is not defined
  // 使用 rest 参数替代
};

// 箭头函数中访问 arguments 会向上查找
function outer() {
  const inner = () => {
    console.log(arguments); // 继承外层的 arguments
  };
  inner();
}
outer(1, 2, 3); // [1, 2, 3]

// 箭头函数应该使用 rest 参数
const arrowSumWithRest = (...args) => {
  console.log(args); // [1, 2, 3, 4, 5]
  return args.reduce((total, num) => total + num, 0);
};
console.log(arrowSumWithRest(1, 2, 3, 4, 5)); // 15

2.4 语法差异

// 普通函数有多种定义方式
// 1. 函数声明
function add1(a, b) { return a + b; }

// 2. 函数表达式
const add2 = function(a, b) { return a + b; };

// 3. 命名函数表达式
const add3 = function sum(a, b) { return a + b; };

// 4. 构造函数(不推荐)
const add4 = new Function('a', 'b', 'return a + b');

// 箭头函数的简洁语法
// 1. 基本形式
const addArrow1 = (a, b) => { return a + b; };

// 2. 省略大括号和return(只有一条表达式时)
const addArrow2 = (a, b) => a + b;

// 3. 单个参数可省略括号
const square = x => x * x;

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

// 5. 返回对象需要括号
const createUser = (name, age) => ({ name, age });

// 6. 多行语句需要大括号
const processData = (data) => {
  const processed = data.filter(item => item.active);
  return processed.map(item => item.value);
};

2.5 方法定义与类中的使用

// 对象方法
const calculator = {
  value: 10,
  
  // 普通函数方法 - this 指向 calculator
  doubleRegular: function() {
    return this.value * 2;
  },
  
  // 箭头函数方法 - this 继承外层,可能不是 calculator
  doubleArrow: () => {
    return this.value * 2; // this 可能是 undefined
  },
  
  // 简写方法(ES6)- 行为类似普通函数
  doubleShorthand() {
    return this.value * 2;
  }
};

console.log(calculator.doubleRegular()); // 20
console.log(calculator.doubleArrow());   // NaN (this.value 是 undefined)
console.log(calculator.doubleShorthand()); // 20

// 类中的方法
class Person {
  constructor(name) {
    this.name = name;
    this.greetArrow = () => {
      console.log(`Arrow: Hello, I'm ${this.name}`);
    };
  }
  
  // 类方法是普通函数,this 指向实例
  greetRegular() {
    console.log(`Regular: Hello, I'm ${this.name}`);
  }
  
  // 箭头函数作为类字段
  greetArrowField = () => {
    console.log(`Arrow Field: Hello, I'm ${this.name}`);
  };
}

const bob = new Person('Bob');
bob.greetRegular();    // "Regular: Hello, I'm Bob"
bob.greetArrow();      // "Arrow: Hello, I'm Bob"
bob.greetArrowField(); // "Arrow Field: Hello, I'm Bob"

// 方法解绑问题
const { greetRegular } = bob;
const { greetArrow } = bob;
const { greetArrowField } = bob;

greetRegular();    // "Regular: Hello, I'm undefined" - this 丢失
greetArrow();      // "Arrow: Hello, I'm Bob" - 箭头函数保持 this
greetArrowField(); // "Arrow Field: Hello, I'm Bob" - 箭头函数保持 this

2.6 生成器函数与 yield

// 普通函数可以是生成器函数
function* regularGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = regularGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2

// 箭头函数不能是生成器函数
// const arrowGenerator = *() => {}; // SyntaxError

// 但可以在箭头函数中使用 yield,如果外层是生成器函数
function* outerGenerator() {
  const inner = () => {
    // 这里不能直接使用 yield
    // 但可以通过 yield* 委托给其他生成器
  };
  
  yield* [1, 2, 3]; // 使用 yield* 委托
}

2.7 call, apply, bind 的影响

const obj1 = { value: 10 };
const obj2 = { value: 20 };

function regularFunc() {
  return this.value;
}

const arrowFunc = () => {
  return this.value;
};

// 普通函数可以通过 call/apply/bind 改变 this
console.log(regularFunc.call(obj1)); // 10
console.log(regularFunc.apply(obj2)); // 20
const boundFunc = regularFunc.bind(obj1);
console.log(boundFunc()); // 10

// 箭头函数的 this 不可改变
console.log(arrowFunc.call(obj1)); // undefined(this 仍然是外层作用域的 this)
console.log(arrowFunc.apply(obj2)); // undefined
const boundArrow = arrowFunc.bind(obj1);
console.log(boundArrow()); // undefined

3. 实际应用场景

3.1 何时使用箭头函数

// 1. 回调函数(尤其是需要保持 this 的情况)
class Timer {
  constructor() {
    this.seconds = 0;
    
    // 错误:普通函数会丢失 this
    // setInterval(function() {
    //   this.seconds++; // this 指向 window
    // }, 1000);
    
    // 正确:箭头函数保持 this
    setInterval(() => {
      this.seconds++;
      console.log(this.seconds);
    }, 1000);
  }
}

// 2. 数组方法回调
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((total, n) => total + n, 0);

// 3. 简短的函数表达式
const add = (a, b) => a + b;
const isEven = n => n % 2 === 0;
const getKey = () => Math.random().toString(36).substr(2);

// 4. 立即执行函数(IIFE)
const result = (() => {
  const x = 10;
  const y = 20;
  return x + y;
})();

// 5. 函数式编程
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const double = x => x * 2;
const increment = x => x + 1;
const doubleThenIncrement = compose(increment, double);
console.log(doubleThenIncrement(5)); // 11 (5*2+1)

3.2 何时使用普通函数

// 1. 构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.introduce = function() {
  return `I'm ${this.name}, ${this.age} years old`;
};

// 2. 对象方法(需要访问对象属性)
const calculator = {
  value: 0,
  add: function(amount) {
    this.value += amount;
    return this;
  },
  getValue: function() {
    return this.value;
  }
};

// 3. 需要 arguments 对象
function sumAll() {
  return Array.from(arguments).reduce((total, num) => total + num, 0);
}

// 4. 生成器函数
function* idGenerator() {
  let id = 1;
  while (true) {
    yield id++;
  }
}

// 5. 递归函数(需要函数名)
const factorial = function(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
};

// 6. 事件处理器(需要 this 指向事件目标)
element.addEventListener('click', function(event) {
  console.log(this); // 指向 element
  console.log(event.target); // 指向点击的元素
});

// 7. 需要被调用时绑定不同 this 的函数
function greet(greeting) {
  return `${greeting}, ${this.name}`;
}
const person = { name: 'Alice' };
console.log(greet.call(person, 'Hello')); // "Hello, Alice"

4. 常见陷阱与解决方案

// 陷阱1:箭头函数作为对象方法
const counter = {
  count: 0,
  
  // 错误:箭头函数不会绑定到 counter
  increment: () => {
    this.count++; // this 指向外层,不是 counter
    console.log(this.count); // undefined
  },
  
  // 正确:使用普通函数或简写方法
  incrementCorrect() {
    this.count++;
    console.log(this.count);
  }
};

// 陷阱2:原型方法
function Animal(name) {
  this.name = name;
}

// 错误:箭头函数作为原型方法
Animal.prototype.speak = () => {
  console.log(`My name is ${this.name}`); // this 不是实例
};

// 正确:使用普通函数
Animal.prototype.speakCorrect = function() {
  console.log(`My name is ${this.name}`);
};

// 陷阱3:动态上下文中的箭头函数
const button = document.createElement('button');
button.textContent = 'Click me';

// 错误:可能想要 this 指向 button
button.addEventListener('click', () => {
  console.log(this); // 指向外层(可能是 window)
  this.style.color = 'red'; // 错误!
});

// 正确:使用普通函数
button.addEventListener('click', function() {
  console.log(this); // 指向 button
  this.style.color = 'red';
});

// 或者使用 event.target
button.addEventListener('click', (event) => {
  console.log(event.target); // 指向 button
  event.target.style.color = 'red';
});

5. 最佳实践总结

使用箭头函数的情况:

  1. 回调函数,尤其是需要保持外层 this 时
  2. 简短的函数表达式,特别是单行函数
  3. 函数式编程中的小函数
  4. 立即执行函数 (IIFE)
  5. 类字段初始化中的箭头函数(用于绑定实例)

使用普通函数的情况:

  1. 构造函数(类)
  2. 对象方法,需要访问对象属性
  3. 需要 arguments 对象
  4. 生成器函数
  5. 递归函数(需要函数名)
  6. 事件处理器,需要 this 指向事件目标
  7. 需要动态绑定 this 的情况

代码示例:

// 好的实践
class Component {
  constructor() {
    this.state = { count: 0 };
    
    // 使用箭头函数绑定 this
    this.handleClick = () => {
      this.setState({ count: this.state.count + 1 });
    };
  }
  
  // 类方法使用普通函数(或简写语法)
  setState(newState) {
    this.state = { ...this.state, ...newState };
    this.render();
  }
  
  render() {
    // 渲染逻辑
  }
}

// 函数式编程
const users = [
  { id: 1, name: 'Alice', active: true },
  { id: 2, name: 'Bob', active: false },
  { id: 3, name: 'Charlie', active: true }
];

const activeUserNames = users
  .filter(user => user.active)           // 箭头函数用于过滤
  .map(user => user.name.toUpperCase())  // 箭头函数用于转换
  .sort((a, b) => a.localeCompare(b));   // 箭头函数用于比较

// 需要 arguments 的情况
function merge(target, ...sources) {     // 使用 rest 参数
  return Object.assign(target, ...sources);
}

// 相当于
function mergeLegacy(target) {
  const result = Object.assign({}, target);
  for (let i = 1; i < arguments.length; i++) {
    Object.assign(result, arguments[i]);
  }
  return result;
}

记住:箭头函数不是要完全替代普通函数,而是提供了另一种更适合特定场景的函数定义方式。理解它们的区别有助于选择最适合的工具来完成工作。