每當提到 JavaScript 的 this
關鍵字,許多開發者都會感到頭痛。它似乎總是以出乎意料的方式工作,讓人捉摸不透。但事實上,一旦你理解了 this
的行為,一切都會變得清晰起來。
this
的基本概念
this
在 JavaScript 中是一個特殊的關鍵字,它代表的是函式執行上下文中的“當前物件”。然而,this
的指向並不是固定的,它取決於函式是如何被呼叫的。這一點在 ECMAScript 規範中有詳細的定義:
tc39.es/ecma262/#se…
並且,如果你不瞭解它的運作機制,有時就會出現一些你認為的莫名其妙的現象。
那麼,這個 this
到底說了什麼內容呢?
說起來也簡單,this
的指向會根據函式的呼叫方式不同而變化。 對於一些常見的函式呼叫模式,我們可以總結如下:
全域性作用域中的
this
當在全域性作用域中呼叫一個函式時,
this
通常指向全域性物件(在瀏覽器中是window
,在 Node.js 中是global
)。作為普通函式呼叫時的
this
當一個函式被直接呼叫時(不是作為一個物件的方法),
this
通常指向全域性物件(非嚴格模式下)或者undefined
(嚴格模式下)。作為物件方法呼叫時的
this
當一個函式作為物件的一個方法被呼叫時,
this
通常指向那個物件。建構函式中的
this
使用
new
關鍵字呼叫建構函式時,this
指向新建立的物件例項。事件處理程式中的
this
當一個函式作為事件處理程式被呼叫時,
this
通常指向觸發事件的元素。箭頭函式中的
this
箭頭函式不會繫結自己的
this
,而是繼承自外圍函式的作用域中的this
。使用
.call
,.apply
,.bind
改變this
可以使用這些方法顯式地設定函式執行時的
this
值。
爲了描述this 的不同繫結方式,程式設計師們達成了共識,給this的繫結也分爲了一下幾類
社羣裡廣泛使用的術語
預設繫結
當函式獨立呼叫時,this指向全域性物件(在瀏覽器環境中為window物件)。
示例:
複製 function sayName() { console.log(this.name); } var name = '全域性名稱'; sayName(); // 輸出:全域性名稱
注意:當這裏是嚴格模式的時候,會訪問undefined導致TypeError
隱式繫結
當函式作為物件的方法呼叫時,this指向該物件。
var person = { name: '張三', sayName: function() { console.log(this.name); } }; person.sayName(); // 輸出:張三
函式呼叫中的this
當一個函式作為普通函式呼叫時,this
的行為取決於上下文:
非嚴格模式下,
this
指向全域性物件。嚴格模式下,
this
為undefined
。
function greet() { console.log(this); } greet(); // 輸出: window (非嚴格模式) 或 undefined (嚴格模式)
箭頭函式中的this
箭頭函式不繫結自己的this
。它們繼承外部函式或全域性作用域中的this
。
const person = { name: 'Alice', greet: () => { console.log('Hello, ' + this.name); } }; person.greet(); // 輸出: Hello, undefined (因為這裏的this是全域性的)
new繫結this
在建構函式中,this
指向新建立的物件例項。
function Person(name) { this.name = name; } const alice = new Person('Alice'); console.log(alice.name); // 輸出: Alice
顯式繫結
透過call、apply和bind方法,可以顯式指定函式的this指向。
示例:
function sayName(age) { console.log(this.name + ',年齡:' + age); } var person = { name: '李四' }; sayName.call(person, 25); // 輸出:李四,年齡:25
案例
來吧,讓我們從最簡單的案例開始看。
function sayHello() { console.log(this); } sayHello(); // 輸出全域性物件(非嚴格模式下)或者 undefined(嚴格模式下)
請仔細閱讀上面的程式碼,然後你認為 sayHello
函式中的 this
是什麼?
要回答這個問題,我們先要了解全域性作用域的this
在全域性作用域(非嚴格模式下),this
指向全域性物件。在瀏覽器環境中,這通常指的是window
物件。
console.log(this); // 輸出: window
在嚴格模式下(use strict
), 全域性作用域中的this
將被設定為undefined
。
'use strict'; console.log(this); // 輸出: undefined
很明顯, 這裏是普通函式的呼叫,且不在嚴格模式下,所以this指向全域性。我們在瀏覽器環境中執行得到的答案就是window,在node環境指向的是globalThis
接下來我們來看一個稍微複雜一點的例子:
const obj = { name: 'Alice', greet: function() { console.log(`Hello, my name is ${this.name}`); } }; obj.greet(); // 輸出 "Hello, my name is Alice"
請仔細閱讀上面的程式碼,然後你認為 greet
函式中的 this
是什麼?
相信你能夠很自信的回答這個問題,greet
函式中的 this
指向 obj
物件。
這個答案是正確的,但如果我追問你是怎麼得到這個答案的,我猜不瞭解 this
行為的你可能會說,因為它是作為一個物件的方法被呼叫的,所以 this
指向 obj
。
這裏的情況是函式作為物件的方法被呼叫,因此遵循第3條規則。所以答案是'Hello, my name is Alice'
接下來我們繼續增加複雜度:
const obj = { name: 'Alice', greet: function() { console.log(`Hello, my name is ${this.name}`); } }; const greet = obj.greet; greet();
這裏我們將 greet
函式賦值給了一個新的變數,並直接呼叫它。你認為 greet
函式中的 this
是什麼? 它依然是作為一個普通函式被呼叫了,遵循第二條,所以如果在瀏覽器環境下列印的仍是windows,但在node環境中會是globalThis
我們再來看看使用 new
關鍵字呼叫建構函式的情況:
function Person(name) { this.name = name; this.sayHello = function() { console.log(`Hello, my name is ${this.name}`); }; } const alice = new Person('Alice'); alice.sayHello(); // 輸出 "Hello, my name is Alice"
這裏我們使用 new
關鍵字建立了一個新的 Person
例項。你認為 sayHello
函式中的 this
是什麼? sayHello
函式中的 this
指向 alice
物件。
這裏的情況是函式作為物件的方法被呼叫,因此遵循第3條規則。此外,alice
物件是在建構函式中建立的,因此遵循第4條規則。
接下來我們看看箭頭函式中的 this
指向:
const obj = { name: 'Alice', greet: () => { console.log(`Hello, my name is ${this.name}`); } }; obj.greet(); // 輸出 "Hello, my name is undefined"
這裏我們使用了箭頭函式。你認為 greet
函式中的 this
是什麼?
greet
函式中的 this
指向全域性物件(非嚴格模式下)或者 undefined
(嚴格模式下)。
這裏的情況是箭頭函式,因此遵循第6條規則,它繼承了外圍函式的作用域中的 this
。
我們再看一個例子
// 定義一個物件 const user = { name: 'Alice', logName: function() { console.log('logName:', this.name); // 1 const innerFunc1 = () => { console.log('innerFunc1:', this.name); // 2 }; const innerFunc2 = () => { console.log('innerFunc2:', this.name); // 3 const innerFunc3 = () => { console.log('innerFunc3:', this.name); // 4 setTimeout(() => { console.log('setTimeout:', this.name); // 5 }, 1000); }; innerFunc3(); }; innerFunc1(); innerFunc2(); } }; // 呼叫物件的方法 user.logName(); // 在全域性作用域中定義一個箭頭函式 const globalArrowFunc = () => { console.log('globalArrowFunc:', this); // 6 }; globalArrowFunc();
先別看下面,你能獨立分析出這裏所有日誌的內容嗎?
分析
logName: 在物件方法
logName
內部,this
指向user
物件,因此this.name
輸出Alice
。innerFunc1: 箭頭函式
innerFunc1
繼承了logName
的this
,因此this.name
輸出Alice
。innerFunc2: 同樣,箭頭函式
innerFunc2
繼承了logName
的this
,因此this.name
輸出Alice
。innerFunc3: 箭頭函式
innerFunc3
也繼承了logName
的this
,因此this.name
輸出Alice
。setTimeout: 即使在
setTimeout
中定義了一個箭頭函式,它依然繼承了logName
的this
,因此this.name
輸出Alice
。globalArrowFunc: 在全域性作用域中定義的箭頭函式,
this
指向全域性物件,因此this
輸出全域性物件。
你都分析對了嗎?
this指向的優先順序
當多種繫結規則同時存在時,它們的優先順序順序為:new繫結 > 顯式繫結 > 隱式繫結 > 預設繫結。
this指向問題到這裏就大致講完了,感謝大家的閱讀
結論
理解this
的指向對於避免常見的JavaScript錯誤至關重要。記住,this
的值是在執行時由呼叫上下文決定的,而箭頭函式則會繼承其封閉作用域的this
。
在開發過程中,確保你清楚每個函式呼叫時this
的預期值,並使用適當的工具和技術來控制或修改它的行為。希望這篇文章對你有所幫助。