Try   HackMD
tags: JavaScript 學習紀錄

[JS] What is “this” in JavaScript? What does “this” refer to?

Outline

  • Simple call (直接的調用)
  • As an object method (物件的調用)
  • Arrow Functions (箭頭函式的調用)
  • As a DOM event handler (DOM 物件調用)
  • Strict mode (嚴格模式調用)
  • call, apply, bind (強制綁定 this 的調用)
  • As a constructor (建構式調用)

Simple call

如果直接調用函式,此函式的 this 會指向 window,以下兩個範例都是直接調用函式,所以都是指向 window

Example 1: this refers to window object

window.name = 'window'; function callName() { console.log('call:', this.name); } callName(); //window

Example 2: 在function內宣並調用, simple call -> window

window.name = 'window'; function callName () { console.log('call:', this.name); // function 內的 function function callAgainName () { console.log('call again:', this.name); } callAgainName(); } callName(); //"call:" "window" //"call again:" "window"

小結: 無論在哪一層,純粹的調用方式 this 都會指向 window

As an object method

如果 function 是在物件下被called,那麼 this 則會指向此object,無論 function 是在哪裡宣告

Example 1

var age = 18; var person = { age: 28, displayAge: displayAge } function displayAge() { console.log(this.age); console.log(this); } displayAge(); // 18 //window person.displayAge(); // 28, //{age: 28, displayAge: ƒ}, 在物件下呼叫,this 則是該物件

Example 2: 把object裡面的function指給一個變數, 但還是用simple call

window.age = 18; var person = { age: 28, displayAge: function () { console.log(this.age); console.log(this); } } //物件內的 function指給一個變數 var callThisName = person.displayAge; callThisName(); // 18, window

Example 3: nested object

window.age = 18; function displayAge() { console.log(this.age); console.log(this); } var person = { age: 28, displayAge: displayAge, nestedPerson: { age:40, displayAge: displayAge } } displayAge(); //18 //window person.displayAge(); //28, //{age: 28, displayAge: ƒ}. this refers to the person object person.nestedPerson.displayAge(); //40 //{age: 40, displayAge: ƒ}. this refers to the nestedPerson object

Arrow functions: they don’t have their “own” this

  • 依據語彙環境的父層區域(parent scope)來綁定。白話的說,arrow function 定義位置(不是呼叫順序)的上一層 this 代表誰,arrow function 內的 this 就代表誰
var person = { age:28, displayAge:function() { const displayAge2 = () => { console.log(this.age); console.log(this); } displayAge2(); //arrow function->parent this } } person.displayAge(); //28, {age: 28, displayAge: ƒ}

As a DOM event handler

  • DOM 搭配 addEventListener 時,this 指的是觸發事件的元素。以下這段程式碼可以貼在任何網頁下的 Console,接下來點擊畫面上任何一區域,該區域則會加上紅線

Example 1: DOM + addEventListener, this 是 DOM boject

const box = document.querySelector('.box') box.addEventListener('click', function(){ console.log(this); // 這裡的 this 是 DOM box })

Example 2: DOM + addEventListener + arrow function, this 是 parent 的 this

const box = document.querySelector('.box') box.addEventListener('click', function(){ console.log(this); // DOM box setTimeout(()=>{ console.log(this); // DOM box. arror function->parent this },500) })

Strict mode: 讓simple call的 this 不再是全域 window, 需要給它this

  • 現在會建議寫 JavaScript 的時候加入 'use strict',這可以改正一些coding不良習慣,但也有可以因此導致專案無法運作,此時可以考慮將 'use strict' 加在函式內,避免影響過去的程式碼及相關套件

Example 1

window.age = 28; function displayAge() { 'use strict'; console.log('call:', this.age); } displayAge(); // Uncaught TypeError: Cannot read properties of undefined (reading 'age')

Example 2:給 strict mode 一個this, object調用 or 綁定都可以

window.age = 38; const person = { age :28, displayAge: function displayAge() { 'use strict'; console.log(this.age); }, } person.displayAge(); //28. object call->this is person obj person.displayAge.call({ age: 18 }); //18. 強制綁this

強制綁定 this

call, bind, apply 這三者都可以傳入新的 this 給予函式使用,使其作為 this 所指向的物件,三者僅是使用方法不同

call

  • fn.call(this, arg1, arg2..., argn)
    • 第一個參數:想要綁定的 this
    • 第二以後的參數:想要傳進目標函式的參數,如果目標函式中不需要參數則不要傳入即可
  • 功能
    • 執行 function
    • 明確指定 this

Example 1: 強制綁定18,永遠的18歲!

var age = 28; function callName() { console.log(this.age); } callName(); // 28 callName.call({age: 18}); // 18

Example 2: 除了傳入給定 this 的參數之外還可以傳入其他argument

var obj1 = { myName: 'obj 1', fn: function (message) { console.log(this.myName + ' ' + message); } } var obj2 = { myName: 'obj 2', } obj1.fn.call(obj2, 'Hello'); //obj 2 Hello

apply

  • fn.apply(this, [arg1, arg2..., argn])
    • 第一個參數:想要綁定的 this
    • 第二個參數:與call類似, 只是第二個參數是陣列而且必須是陣列
  • 功能
    • call
var obj1 = { myName: 'obj 1', fn: function (message) { console.log(this.myName + ' ' + message); } } var obj2 = { myName: 'obj 2', } obj1.fn.call(obj2, ['Hello']); //obj 2 Hello

bind

  • fn.bind(this, arg1, arg2..., argn)
    • 第一個參數:想要綁定的this
    • 想要傳進目標函式的參數,如果目標函式中不需要參數則不要傳入即可
    • 回傳:回傳包裹後的目標函式
  • 功能
    • 明確指定 this
    • 回傳一個包裹函式,當我們執行這個函式時,同時將arguments 一起帶進 Function 中
    • 無論函數怎麼被調用,原始函數的 this 在新函數將永遠與 bind 的第一個參數綁定起來, 只能綁一次的概念

Example 1

function fn() { console.log(this.a); } var bfn = fn.bind({a: 'Mark'}); var b2fn = bfn.bind({a: 'Rose'}); fn(); // undefind, 全域沒有定義a bfn(); // Mark b2fn(); // Mark. bind 只能使用一次!就算再綁Rose也沒用

Example 2: object call + bind

function fn() { console.log(this.a); } var bfn = fn.bind({a: 'Mark'}); var b2fn = bfn.bind({a: 'Rose'}); var obj = {a: 'John', fn: fn, bfn: bfn, b2fn: b2fn}; obj.fn(), obj.bfn(), obj.b2fn(); // John, Mark, Mark. object調用在bind過後會被取代

As a constructor

在建構式下會 new 一個新物件,此時的 this 會指向新的物件

function TeamConstructor () { this.men = 'jamie' } class TeamConstructor { constructor(man) { this.man = man; } } var myTeam = new TeamConstructor(); console.log(myTeam.men); //jamie

整理

  • Simple call: window
  • As an object method: object本身
  • Arrow Functions: parent this
  • As a DOM event handler: DOM object
  • Strict mode: 給它的this
  • call, apply, bind: 強制綁進去的this
  • As a constructor: 建構式本身new object

總結

  • Regular function: how is called ?
  • Arrow function: where is definded ?

Reference

this: Simple call, Object method, DOM event handler, Constructor
Regular function v.s Arrow function
Strict mode
call, apply, bind