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. 最佳实践总结
使用箭头函数的情况:
- 回调函数 ,尤其是需要保持外层
this时 - 简短的函数表达式,特别是单行函数
- 函数式编程中的小函数
- 立即执行函数 (IIFE)
- 类字段初始化中的箭头函数(用于绑定实例)
使用普通函数的情况:
- 构造函数(类)
- 对象方法,需要访问对象属性
- 需要 arguments 对象
- 生成器函数
- 递归函数(需要函数名)
- 事件处理器 ,需要
this指向事件目标 - 需要动态绑定 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;
}
记住:箭头函数不是要完全替代普通函数,而是提供了另一种更适合特定场景的函数定义方式。理解它们的区别有助于选择最适合的工具来完成工作。