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

1. 核心区别概览

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

2. 详细对比分析

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

js 复制代码
// 普通函数的 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

js 复制代码
// 普通函数可以作为构造函数
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 对象

js 复制代码
// 普通函数有 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 语法差异

js 复制代码
// 普通函数有多种定义方式
// 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 方法定义与类中的使用

js 复制代码
// 对象方法
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

js 复制代码
// 普通函数可以是生成器函数
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 的影响

js 复制代码
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 何时使用箭头函数

js 复制代码
// 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 何时使用普通函数

js 复制代码
// 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. 常见陷阱与解决方案

js 复制代码
// 陷阱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 的情况

代码示例:

js 复制代码
// 好的实践
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;
}

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

相关推荐
Lazy_zheng2 小时前
一文搞懂 JavaScript 数据类型转换(显式 & 隐式全解析)
前端·javascript·面试
小宇的天下2 小时前
Virtuoso 中的tech file 详细说明
java·服务器·前端
Zoey的笔记本2 小时前
「软件开发缺陷管理工具」的闭环追踪设计与自动化集成架构
java·服务器·前端
Sapphire~2 小时前
【前端基础】04-XSS(跨站脚本攻击,Cross-Site Scripting)
前端·xss
奔跑的web.2 小时前
Vue 3.6 重磅新特性:Vapor Mode 深度解析
前端·javascript·vue.js
MediaTea2 小时前
Python OOP 设计思想 13:封装服务于演化
linux·服务器·前端·数据库·python
爱敲代码的婷婷婷.2 小时前
patch-package 修改 node_modules流程以及注意点
前端·react native·前端框架·node.js
这是个栗子2 小时前
【API封装参数传递】params 与 API 封装
开发语言·前端·javascript·data·params
凌栀茗2 小时前
html第二天
前端·javascript·html