【前端基礎】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,我們下次見!

為了追求可以窩在座位上、可以心無旁騖思考問題、座位可以亂七八糟沒關係、不需要到處哈腰點頭跑客戶,不用腳踩十公分、連妝都可以不用化的職場人生,文組少女毅然決然踏上RD的養成日常。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
在先前的型別文章中,我們曾經聊過 JavaScript 常用的一些型別,但針對布林這個型別,我們沒有做太多的解釋,原因在於布林值在 JavaScript 會有一個特殊的規則:自動轉型 。 自動轉型可說是讓 JavaScript 為弱型別、且難以管理的最重要的要素,接著就來讓我們來聊聊什麼是自動轉型
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
在前端開發中,很常會有需要轉址的需求,且處理的手法滿因人而異的,所以今天就想要來整理一些常見的 JavaScript 頁面轉址方式,以及各自的差異。
Functional Programming 中文譯作函式程式設計,或是功能性程式設計,常簡稱為:FP,是一種透過使用純函式(Pure Funciton)進行軟體開發,且避免副作用的程式設計典範,比起宣告式的流程控制,在 FP 採用主要以表達式的方式撰寫程式碼。
Hoisting 可以說是 ES6 問世之後,去面試還是會爾偶被問到的面試考題,雖然 Hoisting 離 Modern JavaScript 的技術有點距離,實作上幾乎不太會用到,但透過了解 Hoisting 的概念,可以對這門語言有更深的了解與掌握度。
對於剛接觸前端開發不久的人來說,可能會對var、let 與 const 的差異略懂略懂,但又說不太出三者實際哪裡不一樣。
在先前的型別文章中,我們曾經聊過 JavaScript 常用的一些型別,但針對布林這個型別,我們沒有做太多的解釋,原因在於布林值在 JavaScript 會有一個特殊的規則:自動轉型 。 自動轉型可說是讓 JavaScript 為弱型別、且難以管理的最重要的要素,接著就來讓我們來聊聊什麼是自動轉型
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
在前端開發中,很常會有需要轉址的需求,且處理的手法滿因人而異的,所以今天就想要來整理一些常見的 JavaScript 頁面轉址方式,以及各自的差異。
Functional Programming 中文譯作函式程式設計,或是功能性程式設計,常簡稱為:FP,是一種透過使用純函式(Pure Funciton)進行軟體開發,且避免副作用的程式設計典範,比起宣告式的流程控制,在 FP 採用主要以表達式的方式撰寫程式碼。
Hoisting 可以說是 ES6 問世之後,去面試還是會爾偶被問到的面試考題,雖然 Hoisting 離 Modern JavaScript 的技術有點距離,實作上幾乎不太會用到,但透過了解 Hoisting 的概念,可以對這門語言有更深的了解與掌握度。
對於剛接觸前端開發不久的人來說,可能會對var、let 與 const 的差異略懂略懂,但又說不太出三者實際哪裡不一樣。
你可能也想看
Google News 追蹤
Thumbnail
現代社會跟以前不同了,人人都有一支手機,只要打開就可以獲得各種資訊。過去想要辦卡或是開戶就要跑一趟銀行,然而如今科技快速發展之下,金融App無聲無息地進到你生活中。但同樣的,每一家銀行都有自己的App時,我們又該如何選擇呢?(本文係由國泰世華銀行邀約) 今天我會用不同角度帶大家看這款國泰世華CUB
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
在前端開發的世界中,有許多工具和技術能幫助我們有效地建立、維護和優化應用。本文將探討如何下載Node.js、使用CDN獲取套件、使用npm下載和管理套件、以及打包與tree-shaking的概念,這部分是前端很常會使用到的喔。 下載Node.js Node.js是前端開發中不可或缺的工具,它
Thumbnail
2024年韓國盃半決賽即將上演,濟州聯隊挑戰衛冕冠軍浦項制鐵,光州FC將迎戰蔚山現代。首輪較量將於8月21日開啟,濟州與浦項在濟州主場展開較量。
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
Thumbnail
在本章節中,我們將學習JavaScript的基本語法,包括如何註解代碼和如何聲明變數。瞭解這些基礎知識對於進一步學習和使用JavaScript來編寫代碼是非常重要的。
Thumbnail
JavaScript是一種具有動態型別、弱型別、原型繼承等特性的高級腳本語言,應用範圍廣泛,包括前端開發、後端開發、移動應用等。它被各種公司和開源社區廣泛使用。學習JavaScript需要掌握ECMAScript標準、異步編程、模塊系統等知識。
※ ES6 變數宣告介紹: 在ES6中,推薦使用let和const取代原有的var來宣告變數。 ※ var的特點: 勢力範圍(scope)只有兩種:function、global(全域部分)。 勢力範圍(scope)指的是宣告變數的範圍,能夠被有效的使用的範圍。 可以在宣告變數之前就使用。
※ 函式進階介紹: 箭頭函式: 箭頭函式相比於一般函式,語法相當簡潔。除了少去 function 關鍵字,如果只有一個參數,箭頭函式可以省略括號;只有一行程式碼,就是直接簡單返回一個變數或簡單的表達式,可以省略大括號和 return。例子如下: //一般函式計算平方用的寫法 const squ
Thumbnail
在前端的開發中,除了切版與串 API 外,大部分的時間都在針對表單內容進行檢核、驗證、阻擋,一方面是讓使用者在操作頁面的過程中有良好的使用者體驗,不會因為一些例外狀況(Edge Case),例如:莫名其妙的 4xx 錯誤,導致使用者卡在某個操作流程中逃不出來,另一方面是讓傳遞到後端的資料更加正確⋯⋯
Thumbnail
在 2021 年的剛轉職成為前端工程師的時候,我在面試時滿常會被詢問到 JavaScript 中閉包的議題,當時候自己回答的滿差的,於是在 2022 年時,我寫了一系列的有關於函式程式設計鐵人賽的文章, 裡頭就有簡單提到有關於閉包的議題。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
Thumbnail
現代社會跟以前不同了,人人都有一支手機,只要打開就可以獲得各種資訊。過去想要辦卡或是開戶就要跑一趟銀行,然而如今科技快速發展之下,金融App無聲無息地進到你生活中。但同樣的,每一家銀行都有自己的App時,我們又該如何選擇呢?(本文係由國泰世華銀行邀約) 今天我會用不同角度帶大家看這款國泰世華CUB
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
在前端開發的世界中,有許多工具和技術能幫助我們有效地建立、維護和優化應用。本文將探討如何下載Node.js、使用CDN獲取套件、使用npm下載和管理套件、以及打包與tree-shaking的概念,這部分是前端很常會使用到的喔。 下載Node.js Node.js是前端開發中不可或缺的工具,它
Thumbnail
2024年韓國盃半決賽即將上演,濟州聯隊挑戰衛冕冠軍浦項制鐵,光州FC將迎戰蔚山現代。首輪較量將於8月21日開啟,濟州與浦項在濟州主場展開較量。
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
Thumbnail
在本章節中,我們將學習JavaScript的基本語法,包括如何註解代碼和如何聲明變數。瞭解這些基礎知識對於進一步學習和使用JavaScript來編寫代碼是非常重要的。
Thumbnail
JavaScript是一種具有動態型別、弱型別、原型繼承等特性的高級腳本語言,應用範圍廣泛,包括前端開發、後端開發、移動應用等。它被各種公司和開源社區廣泛使用。學習JavaScript需要掌握ECMAScript標準、異步編程、模塊系統等知識。
※ ES6 變數宣告介紹: 在ES6中,推薦使用let和const取代原有的var來宣告變數。 ※ var的特點: 勢力範圍(scope)只有兩種:function、global(全域部分)。 勢力範圍(scope)指的是宣告變數的範圍,能夠被有效的使用的範圍。 可以在宣告變數之前就使用。
※ 函式進階介紹: 箭頭函式: 箭頭函式相比於一般函式,語法相當簡潔。除了少去 function 關鍵字,如果只有一個參數,箭頭函式可以省略括號;只有一行程式碼,就是直接簡單返回一個變數或簡單的表達式,可以省略大括號和 return。例子如下: //一般函式計算平方用的寫法 const squ
Thumbnail
在前端的開發中,除了切版與串 API 外,大部分的時間都在針對表單內容進行檢核、驗證、阻擋,一方面是讓使用者在操作頁面的過程中有良好的使用者體驗,不會因為一些例外狀況(Edge Case),例如:莫名其妙的 4xx 錯誤,導致使用者卡在某個操作流程中逃不出來,另一方面是讓傳遞到後端的資料更加正確⋯⋯
Thumbnail
在 2021 年的剛轉職成為前端工程師的時候,我在面試時滿常會被詢問到 JavaScript 中閉包的議題,當時候自己回答的滿差的,於是在 2022 年時,我寫了一系列的有關於函式程式設計鐵人賽的文章, 裡頭就有簡單提到有關於閉包的議題。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。