在 JavaScript 中,箭头函数(Arrow Functions)和普通函数(Regular Functions)有以下主要区别:
1. 语法
-
箭头函数 :使用
=>语法,更简洁,可省略function和return(单行表达式时)。// 普通函数 function add(a, b) { return a + b; } // 箭头函数 const add = (a, b) => a + b; -
普通函数 :使用
function关键字定义。function sayHello() { console.log("Hello"); }
2. this 指向
-
箭头函数:
- 不绑定
this,继承自父级作用域(定义时的上下文)。 - 适合不需要自己上下文的场景(如回调函数)。
const obj = { // 全局作用域(Global Scope) name: "Alice", // obj 对象作用域(Object Scope) greet: function() { // greet 方法作用域(Function Scope) setTimeout(() => { // 箭头函数作用域(Arrow Function Scope) console.log(`Hello, ${this.name}`); }, 1000); } }; obj.greet();-
箭头函数的
this继承自哪里?箭头函数的
this继承自它被定义时的外层作用域的this(即greet函数的this),而 不是 继承外层函数(greet)本身。-
greet是一个普通函数,它的this由调用方式决定(obj.greet()所以this = obj)。 -
箭头函数继承了
greet的this(即obj),而不是greet函数对象。
-
-
为什么不是指向
greet函数?-
函数本身的
this和函数对象是 完全不同的概念。 -
greet作为函数对象时,它的名字是greet,但它的this是动态绑定的(这里是obj)。const obj = {
name: "Alice",
greet: function() {
setTimeout(() => {
console.log(Hello, ${this.name}); // 继承自 greet() 的 this
}, 1000);
}
};
obj.greet(); // "Hello, Alice"
-
- 不绑定
-
普通函数:
-
绑定自己的
this,指向调用该函数的对象(或全局对象 / 严格模式下为undefined)。const obj = {
name: "Bob",
greet: function () {
console.log(this.name); // "Bob"
setTimeout(function () {
console.log(Hello, ${this.name}); // this 指向全局对象或 undefined
}, 1000);
}
};
obj.greet(); // "Hello, undefined" 或报错
-
-
const/let声明的全局变量 不会 成为全局对象(如window)的属性。 -
只有
var声明的变量或直接赋值到this的属性(如window.age = 20)才会被this.age访问到。var age = 20; // var 会挂载到 window
/// const age = 20 //const 是局部变量
const obj = {
greet: function() {
setTimeout(function() {
console.log(this.age); // 20(this 指向 window)
}, 1000);
}
};
obj.greet();
3. arguments 对象
-
箭头函数:
-
没有自己的
arguments对象,继承自父级作用域。const sum = () => {
console.log(arguments); // 报错或引用外层的 arguments
};
sum(1, 2); // 错误:arguments 未定义
-
-
普通函数:
-
有自己的
arguments对象,包含调用时的参数。function sum() {
return arguments[0] + arguments[1];
}
sum(1, 2); // 3
-
4. 使用限制
-
箭头函数:
-
不能使用
arguments、super或new.target。 -
不能使用
yield(不能用作生成器)。 -
不能使用
new实例化 (没有[[Construct]]方法)。const ArrowClass = () => {};
new ArrowClass(); // 错误:箭头函数不能用作构造函数
-
-
普通函数:
-
可以使用
arguments、super、new.target和yield。 -
可以使用
new实例化(作为构造函数)。function RegularClass() {
this.value = 42;
}
const instance = new RegularClass();
-
5. yield 和 await
-
箭头函数:
-
不能单独使用
yield(除非在生成器函数内部)。 -
可以使用
await,但需要在async箭头函数中。const fetchData = async () => {
const response = await fetch("api/data");
return response.json();
};
-
-
普通函数:
-
可以使用
yield定义生成器函数。function* generator() {
yield 1;
yield 2;
}
-
6. 适用场景
-
箭头函数:
- 简单的回调函数(如
map、filter、setTimeout)。 - 需要保留父级
this的场景。 - 单行逻辑的函数。
- 简单的回调函数(如
-
普通函数:
- 构造函数(需要创建实例)。
- 需要动态
this绑定的场景(如方法、事件处理)。 - 需要使用
arguments对象的场景。
总结对比表
| 特性 | 箭头函数 | 普通函数 |
|---|---|---|
| 语法 | 简洁,使用 => |
完整,使用 function |
this 指向 |
继承自父级作用域 | 动态绑定(调用者) |
arguments 对象 |
无 | 有 |
能否使用 new |
否 | 是 |
能否使用 yield |
否(除非在生成器内) | 是 |
| 适用场景 | 回调函数、需要保留 this |
构造函数、动态 this 绑定 |
如何理解this 指向 箭头函数的this继承自父级作用域 普通函数的this是动态绑定(调用者)
要理解 this 指向的差异,关键在于抓住箭头函数的 this 是 "静态绑定"(定义时确定),而普通函数的 this 是 "动态绑定"(调用时确定) 这一核心区别。下面通过具体场景和例子展开说明:
一、箭头函数:this 继承自父级作用域(定义时确定)
箭头函数没有自己的 this,它的 this 是在定义函数时 就固定好的,等于父级作用域(外层代码块)的 this,且永远不会改变。
可以理解为:箭头函数的 this 是 "抄" 父级的,一旦定义就 "锁死",后续无论怎么调用,this 都不会变。
例子 1:对象方法中的箭头函数
const obj = {
name: "Alice",
// 普通函数作为方法(父级作用域)
getParentThis: function() {
// 箭头函数定义在 getParentThis 内部,父级作用域是 getParentThis 的 this
const arrowFunc = () => {
console.log(this.name); // this 继承自 getParentThis 的 this(即 obj)
};
return arrowFunc;
}
};
const func = obj.getParentThis();
func(); // 输出 "Alice"(箭头函数的 this 是定义时的父级 this,即 obj)
- 箭头函数
arrowFunc定义在getParentThis内部,父级作用域的this是obj(因为getParentThis是obj调用的),所以箭头函数的this就是obj。 - 即使后续用其他方式调用
func(如func.call(otherObj)),this也不会变,始终是obj。
例子 2:全局作用域中的箭头函数
// 全局作用域的 this 是 window(浏览器环境)
const globalArrow = () => {
console.log(this === window); // 输出 true(继承全局作用域的 this)
};
globalArrow();
const obj = { fn: globalArrow };
obj.fn(); // 依然输出 true(箭头函数的 this 不会因调用者变化而改变)
二、普通函数:this 是动态绑定(调用时确定,由调用者决定)
普通函数的 this 是在调用函数时 才确定的,取决于 "谁调用了它",即 "调用者"。调用方式不同,this 指向就可能不同。
常见的调用场景决定 this 指向的规则:
- 直接调用 (如
fn()):this指向全局对象(浏览器中是window,Node 中是global;严格模式下为undefined)。 - 作为对象方法调用 (如
obj.fn()):this指向该对象(obj)。 - 用
call/apply/bind调用 :this指向传入的第一个参数。 - 作为构造函数调用 (如
new Fn()):this指向新创建的实例对象。
例子 1:不同调用方式下的普通函数 this
function regularFunc() {
console.log(this.name);
}
const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };
// 1. 直接调用:this 指向全局(无 name 属性,输出 undefined)
regularFunc(); // undefined
// 2. 作为对象方法调用:this 指向调用的对象
obj1.fn = regularFunc;
obj1.fn(); // 输出 "obj1"(this 是 obj1)
// 3. 用 call 强制绑定:this 指向传入的 obj2
regularFunc.call(obj2); // 输出 "obj2"
例子 2:对比箭头函数和普通函数在回调中的差异
最典型的场景是定时器回调:
const person = {
name: "Bob",
// 普通函数作为方法
sayHi: function() {
// 1. 普通函数作为回调:this 指向全局(调用者是定时器,非 person)
setTimeout(function() {
console.log("普通函数回调:", this.name); // undefined(this 是 window)
}, 100);
// 2. 箭头函数作为回调:this 继承自 sayHi 的 this(即 person)
setTimeout(() => {
console.log("箭头函数回调:", this.name); // "Bob"(this 是 person)
}, 100);
}
};
person.sayHi();
- 普通函数的回调:调用者是定时器(浏览器中是
window),所以this指向window。 - 箭头函数的回调:定义在
sayHi内部,父级sayHi的this是person,所以箭头函数的this也是person。
总结:核心区别一句话
- 箭头函数 :
this是 "定义时抄父级的",一旦确定就不变(静态绑定)。 - 普通函数 :
this是 "调用时看是谁调的",调用方式变了,this就可能变(动态绑定)。
记住这个区别,就能避开大多数 this 指向的坑。