【前端基礎】JavaScript 的 this 到底是什麼?call、apply 與 bind 的差異

更新於 發佈於 閱讀時間約 11 分鐘

在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)

在 JavaScript 中的物件,被生成後會出現一個參考位置,如果有其他變數或是在使用函式運算的過程中,會修改到這個物件,所有沿用(例如:拿這個物件去賦予給其他變數)、有使用到這個物件的地方,都會因為這個物件被改動,而連帶被受到影響,這樣的機制就稱為傳共享,因為所有的變數其實都共用了這個物件。

在理解 JavaScript 中 this 以前,理解 JavaScript 中的物件與傳共享機制是非常必要的,如果你已經夠熟悉這些機制的話就讓我們看下去吧。

註:這邊所提到所有內容都是基於在瀏覽器中執行的情境為主,在 Node.js 環境中的 this 概念略有不同。

JavaScript 的 this 到底是什麼?

**this 是一個被 JavaScript 保留的關鍵字,是一種抽象概念上的參考物件,也就是說:當我們使用到了 this ,這個 this 實際上指的某個參考物件。**

這裡為了避免誤會,我再講陳述的清楚一點: this 實際上指的某個參考物件,並不是某個物件,因為同一個物件可以被很多變數沿用、甚至在短時間內被不同的函式操作。

理解完 this 是一種參考物件後,再來聊聊其最重要的特性:會受範圍鏈(Scope Chain)以及被呼叫的位置(要根據上下文決定)來決定這個參考物件到底會是什麼。

全域中的的 this

之前在講 varletconst 的差異時,也有提到所謂範圍鏈的概念,如果不是那麼清楚的人可以參考看看這篇文章

在全域底下的 this 其實就是 window 物件,不過在嚴格模式中是取用不到 window 物件的(而且會報錯),所以同理上不會取用到 this 的值,不過並不會報錯,而是會回傳 undefined

'use strict';

console.log(this);
// -> undefined

講到這邊,我自己本人目前接觸前端已經三年了,是沒有遇到有需要使用到全域 this 的狀況,看一些 Youtube 上的超級資深網頁開發者會分享,以前為了方便所以會把一些方法實作後掛載到 window 上,不過前後端分離後這樣的時代也漸漸遠去,還需要理解這個東西的原因在於「理解 JavaScript 」機制,以及面試滿常考到的。

在函式中調用 this

JavaScript 是一種擁有多重設計典範的語言,我們可以在 JavaScript 撰寫物件導向風格的程式碼,也可以撰寫函式程式設計方格的程式碼,而 this 就比較偏向前者的風格。

我們可以透過在函式中使用 this 取用到物件的狀態,舉例來說:

const person = {
name: 'Jack',
age: 1,
gender: 'male',
print: function() {
console.log(`Jack is ${this.age} years old.`)
}
};

person.print();

// -> Jack is 1 years old.

由於受範圍鏈影響, this 實際上會指向到哪一層物件,是透過向外層查找的方式來決定的,萬一沒有外層的那一層物件怎麼辦?

function print () {
console.log(`Jack is ${this.age} years old.`)
};

print();
/ -> ?

那就會是 undefined ,因為此時外層並沒有物件,當然這也不是絕對的答案,在不嚴格的模式下,如果你的全域物件上有 age 這個屬性,也許答案會有所不同。

age = 11;

function print () {
console.log(`Jack is ${this.age} years old.`)
};

print();
/ -> ?

看到這裡如果你會覺得有夠詭異的話,我推薦你閱讀我之前寫的有關於 JavaScript 變數、全域屬性的這篇文章

箭頭函式中沒有 this

在先前的範例中,可以發現幾乎都是使用「函式陳述式(Function Statement)」,原因是箭頭函式其實被設計成沒有 this 的存在,這樣的設計是為了在一些 Callback 的情境中可以保留指定的 this 狀態,例如:

function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}

var timer = new Timer();

因為箭頭函式本身沒有 this ,所以會透過範圍鏈往上查找最近的 this ,簡單來說,可以透過不同函式的運作方式,來決定 this 的影響範疇。

個人是覺得這樣的機制是沒有到很方便,甚至我也不太會用到,也是一種屬於了解 JavaScript 這門語言的切入點。

動態改變 this 的指向:call、apply 與 bind

this 除了會受範圍鏈影響外,也可以透過一些方式來綁定參考物件做到動態的參考物件指向,通常是透過 call、apply 與 bind 這三個函式來做到,接著就來看看這三者有什麼樣的差異:

  • call() :是一個可以傳入多個參數的函式方法,主要用於動態指向參考物件,第一個參數帶入參考物件,第二個以後的參數則是函式本身,語法為:
fun.call(thisArg[, arg1[, arg2[, ...]]])

舉例來說,透過 call() 方法,我們將 print() 函式的參考物件指給另外一個 person 物件。


const person = {
name: 'Jack',
age: 1,
gender: 'male',
};

function print (time) {
console.log(`${time}: ${this.name} is ${this.age} years old.`)
};

print.call(person, '2023/01/01');
// -> 2023/01/01: Jack is 1 years old.
  • apply() :是一個可以傳入兩個參數的函式方法,主要用於動態指向參考物件,第一個參數帶入參考物件,第二個的參數是陣列,依序帶入此函式所需要的參數,語法為:
fun.apply(thisArg, [argsArray])

我們可以用 apply 改寫上方的範例:

const person = {
name: 'Jack',
age: 1,
gender: 'male',
};

function print (time) {
console.log(`${time}: ${this.name} is ${this.age} years old.`)
};

print.apply(person, ['2023/01/01']);
// -> 2023/01/01: Jack is 1 years old.
  • bind() :使用方式跟 call 很像,只是會再額外回傳一個新的函式,屬於靜態綁定 this 的方式,若要使用就需要額外呼叫,兩者的關係很像 forEach 之於 map 這樣,語法為:
fun.bind(thisArg[, arg1[, arg2[, ...]]])

延續上方的範例,我們使用 bind 改寫,綁定完 this 還需要再呼叫一次函式才會再次執行:

const person = {
name: 'Jack',h
age: 1,
gender: 'male',
};

function print (time) {
console.log(`${time}: ${this.name} is ${this.age} years old.`)
};

// 做法一:像 call 一樣帶入函式原本的 n 個參數
const bindPrint = print.bind(person, '2023/01/01');

bindPrint();
// -> 2023/01/01: Jack is 1 years old.

// 做法二:科里化用法
const bindPrint = print.bind(person);

bindPrint('2023/01/01');
// -> 2023/01/01: Jack is 1 years old.

關於科里化作法是什麼,可以參考這篇文章

動手做做看:

最近我課金買了 chatGPT-4 的模型,我請它出了 this 的面試題,各位可以做做看來確認自己對於 this 的理解是否正確,需要注意的題目是有一點小陷阱的:

function Employee(name, position) {
this.name = name;
this.position = position;
this.details = function() {
return this.name + " is a " + this.position;
}
}

let emp1 = new Employee('John', 'Engineer');
let emp2 = new Employee('Jane', 'Manager');

let empDetails = emp1.details;

console.log(empDetails());

let empDetailsBound = emp1.details.bind(emp2);

console.log(empDetailsBound());

let empDetailsCall = emp1.details.call(emp2);

console.log(empDetailsCall);

let empDetailsApply = emp1.details.apply(emp2);

console.log(empDetailsApply);

接下來就留給各位自己練習啦,希望今天的文章可以讓大家更加理解 this 的用法跟原理,我是 Vivian,我們下次見!

留言
avatar-img
留言分享你的想法!
avatar-img
Vivian Yeh - 跨領域轉職的軟體工程師
447會員
103內容數
為了追求可以窩在座位上、可以心無旁騖思考問題、座位可以亂七八糟沒關係、不需要到處哈腰點頭跑客戶,不用腳踩十公分、連妝都可以不用化的職場人生,文組少女毅然決然踏上RD的養成日常。
2024/05/23
與 cookie 相比,localStorage 與 sessionStorage 的機制相對單純,兩者皆是瀏覽器中的儲存空間,與 cookie 最大的不同在於:localStorage 與 ⋯⋯
Thumbnail
2024/05/23
與 cookie 相比,localStorage 與 sessionStorage 的機制相對單純,兩者皆是瀏覽器中的儲存空間,與 cookie 最大的不同在於:localStorage 與 ⋯⋯
Thumbnail
2024/05/22
在瀏覽器環境中有許多的儲存空間,想要查看這些空間的話,可以透過「chrome > Dev Tools > Application > Storage」即能進行查看。 瀏覽器內存空間的差異不僅常常被拿來被當作面試考題,在實務開發中更扮演舉足輕重的角色,今天就想透過這系列的文章深度了解這些瀏覽器內存⋯
Thumbnail
2024/05/22
在瀏覽器環境中有許多的儲存空間,想要查看這些空間的話,可以透過「chrome > Dev Tools > Application > Storage」即能進行查看。 瀏覽器內存空間的差異不僅常常被拿來被當作面試考題,在實務開發中更扮演舉足輕重的角色,今天就想透過這系列的文章深度了解這些瀏覽器內存⋯
Thumbnail
2024/02/20
上一篇文章分享了 TypeScript 的定義、前端角色定位,如果你不是很確定「TypeScript 是什麼?」、「TypeScript 作為 JavaScript 的超集,在網頁開發扮演怎麼樣的角色?」這兩個問題的答案,建議可以回到上一篇先了解一下。
Thumbnail
2024/02/20
上一篇文章分享了 TypeScript 的定義、前端角色定位,如果你不是很確定「TypeScript 是什麼?」、「TypeScript 作為 JavaScript 的超集,在網頁開發扮演怎麼樣的角色?」這兩個問題的答案,建議可以回到上一篇先了解一下。
Thumbnail
看更多
你可能也想看
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
JSDoc 全名是 JavaScript Documentation,顧名思義是為 JavaScript 所使用的 API 文件,在程式碼內透過註解的方式撰寫,運行後 JSDoc 會自動掃描註解內容,並生成一份網頁版的文件,對於沒有使用 Typescript 開發的專案,也
Thumbnail
JSDoc 全名是 JavaScript Documentation,顧名思義是為 JavaScript 所使用的 API 文件,在程式碼內透過註解的方式撰寫,運行後 JSDoc 會自動掃描註解內容,並生成一份網頁版的文件,對於沒有使用 Typescript 開發的專案,也
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
Thumbnail
JavaScript (簡稱 JS) 是具有一級函數的輕量級、直譯式或即時編譯的程式語言。它因為用作網頁的腳本語言而大為知名,但也用於許多非瀏覽器的環境,像是 Node.js 等。由於 JavaScript 語法上的一些缺點,軟體工程師們又設計出了 CoffeeScript、TypeScript 和
Thumbnail
JavaScript (簡稱 JS) 是具有一級函數的輕量級、直譯式或即時編譯的程式語言。它因為用作網頁的腳本語言而大為知名,但也用於許多非瀏覽器的環境,像是 Node.js 等。由於 JavaScript 語法上的一些缺點,軟體工程師們又設計出了 CoffeeScript、TypeScript 和
Thumbnail
這些章節的目的是為了介紹JavaScript中的各種數據類型,包括基礎類型和物件類型,以及如何將數據從一種類型轉換為另一種類型。此外,還介紹了如何創建自定義類型,以及如何使用JavaScript中的陣列、集合和字典。
Thumbnail
這些章節的目的是為了介紹JavaScript中的各種數據類型,包括基礎類型和物件類型,以及如何將數據從一種類型轉換為另一種類型。此外,還介紹了如何創建自定義類型,以及如何使用JavaScript中的陣列、集合和字典。
Thumbnail
JavaScript是一種具有動態型別、弱型別、原型繼承等特性的高級腳本語言,應用範圍廣泛,包括前端開發、後端開發、移動應用等。它被各種公司和開源社區廣泛使用。學習JavaScript需要掌握ECMAScript標準、異步編程、模塊系統等知識。
Thumbnail
JavaScript是一種具有動態型別、弱型別、原型繼承等特性的高級腳本語言,應用範圍廣泛,包括前端開發、後端開發、移動應用等。它被各種公司和開源社區廣泛使用。學習JavaScript需要掌握ECMAScript標準、異步編程、模塊系統等知識。
Thumbnail
針對 JavaScript 中的原始型別和隱性轉型進行了詳細的探討
Thumbnail
針對 JavaScript 中的原始型別和隱性轉型進行了詳細的探討
Thumbnail
可選串聯(?.)運算符用於訪問 object 的屬性或調用函數。如果使用該運算符訪問的object 或調用的函式為 undefined 或 null,則表達式會回傳 undefined,而不是拋出錯誤。
Thumbnail
可選串聯(?.)運算符用於訪問 object 的屬性或調用函數。如果使用該運算符訪問的object 或調用的函式為 undefined 或 null,則表達式會回傳 undefined,而不是拋出錯誤。
Thumbnail
學習JavaScript的理由有很多,包括容易學習的程式語言、互動式體驗、多功能性、跨平臺、社群和資源豐富、高市場需求。此外,文章提供了設計和前端教學的相關資源連結。文章中還提到了一些與學習JavaScript相關的教學文章和影音教學資源。
Thumbnail
學習JavaScript的理由有很多,包括容易學習的程式語言、互動式體驗、多功能性、跨平臺、社群和資源豐富、高市場需求。此外,文章提供了設計和前端教學的相關資源連結。文章中還提到了一些與學習JavaScript相關的教學文章和影音教學資源。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
Thumbnail
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
Thumbnail
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News