在 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 的 thisconst 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
?指向的坑。