iT邦幫忙

2021 iThome 鐵人賽

DAY 30
0
Modern Web

我的JavaScript日常系列 第 30

JavaScript Day 30. 關於 JavaScript 中的 This

第 30 天,本來想說或許最後一天可以來一篇心得文,讓自己好好休息一下,因為這 30 天花了大量的精神在一邊找資料一邊吸收知識順便應付職場,整體覺得需要出去吃喝玩樂放爛一天╰(°▽°)╯

但又突然發現,我是跟著這 30 天結束了就會停止學習了嗎?又或者是這 30 天之後我就不會再碰任何程式語言了?好像也不是,那假如不是的話,30 天之後再休息好像也是可以的 XD;所以,我們就繼續來討論個什麼吧 ˊ_>ˋ

this 到底指向誰?

想要理解 this,我們可以記住兩個關鍵,第一 this 永遠指向一個對象,第二 this 的指向取決於函式的調用以及呼叫方式。this 預設指向為全域性物件 window,並且 this 只能指向物件,哪個物件呼叫函式,函式裡的 this 就預設指向哪個物件。

this 的調用分成幾種:

  • 純粹的調用 ( Simple call )
  • 物件方法調用 ( As an object method )
  • DOM 物件調用
  • 建構式調用
  • 透過 call、apply、bind 調用 this
  • 重新指向 this

純粹的調用 ( Simple call )

直接調用函式的 this 會指向 window

window.doraemon = '哆啦A夢';
function callDoraemon() {
  console.log('call:', this.doraemon);
  // this == window
  // 所以此時的 this.doraemon 一樣可以取得 window 下的 doraemon
}

callDoraemon(); // call: 哆啦A夢

另外這個例子是 function 包覆 function,不過這裡的 this 依然是會指向全域,因為我們是直接呼叫,使用直接調用的方式不論在哪一層的 this 都會指向全域。

window.sonOfOdin = '索爾';
function callSonOfOdin () {
  console.log('call:', this.sonOfOdin); // call: 索爾

  // function 內的 function
  function callAgainSonOfOdin () {
    console.log('call again:', this.sonOfOdin);
  }
  callAgainSonOfOdin(); // call again: 索爾
}

callSonOfOdin(); 

物件方法調用 ( As an object method )

物件方法調用的重點在於,是在哪一個物件下被呼叫,以下範例會先給一個純粹調用的例子,然後才會是物件方法調用的範例。

// 物件方法調用
function callAvengers() {
  console.log(this.avengers);
}

var avengers = 'iron Man';
var hulk = {
  avengers: 'hulk',
  callAvengers: callAvengers 
}

callAvengers()        // iron Man
hulk.callAvengers() // hulk,this 在 hulk 物件下被調用,因此指向 hulk

宣告的方法不重要,重要的是我們怎麼呼叫,如果我們將物件內的函式賦予在一個變數上並調用它。這個 this 也會指向全域。

var avengers = 'iron Man';
var hulk = {
  avengers: 'hulk',
  callAvengers: function () {
    console.log(this.avengers);
  }
}

callTheAvengers = hulk.callAvengers; 
callTheAvengers() // iron Man

DOM 物件調用

DOM 搭配 addEventListener 時,this 的指向則是該 DOM。這裡卡斯伯老師說:接下來點擊畫面任何一區域,該區域則會加上紅線。

var e = document.getElementsByTagName('div');
function changeDOM() {
  console.log(this); // 指向當前的 DOM
  this.style.border = '1px solid red'
}

for (var i = 0; i < e.length; i++) {
  e[i].addEventListener('click', changeDOM, false);
}

這邊我們另外再參考一個簡單的範例,這裡不論點到哪裡都會顯示我們所點到的資訊,表示此時的 this 會指向我們點擊到的 DOM。

let e = document.querySelector('body');

function changeDOM() {
  console.log(this);  // <body>...</body>
}

e.addEventListener('click', changeDOM);

建構式調用

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

function mickeyMouse () {
  this.mouse = '米奇'
}

var myFavourite = new mickeyMouse();
console.log(myFavourite.mouse); // 米奇

這裡的 this 不是全域,而且可以在新生成的物件上重新定義,所以他會指向新生成的物件。

var disney = 'Mickey Mouse';
function disneySeries(role) {
  this.disney = role || 'Donald Duck';
}

var myFavourite = new disneySeries('Mickey');
var ordinary = new disneySeries();
console.log('我最喜歡', myFavourite.disney);    // 我最喜歡 Mickey
console.log('覺得還好', ordinary.disney);  // 覺得還好 Donald Duck

透過 call、apply、bind 調用 this

call()apply() 呼叫函式,然後我們設定 this 接著傳入其他參數。

// 範例 1
let obj = {};

function foo() {
  console.log(this);
}

foo(); // "Window{}"
foo.call(obj); // Object{}
foo.apply(obj); // Object{}

兩者第一個參數都是 this 值,也就是要綁定的物件,差異在於後面的參數。

// 範例 2
let obj = {};

function foo(a, b) {
  console.log(this, a, b);
}

foo.call(obj, 1, 2); // Object{} 1 2
foo.apply(obj, [1, 2]); // Object{} 1 2

call() 呼叫與平常函式呼叫方法相同,apply() 則需要使用陣列包起來。

bind() 會回傳一個新的函式,當被呼叫時,會將提供的值設為 this 值。

// 範例 1
function foo() {
  console.log(this.name);
}

let obj = {
  name: 2,
};

let boo = foo.bind(obj);

boo(); // 2
setTimeout(boo, 100); // 2
boo.call(window); // 2

bind() 方法第一個也是 this 後面的參數則跟函式一樣。

// 範例 2
let obj = {};

function foo(a, b) {
  console.log(this, a, b);
}

const myFoo = foo.bind(obj, 1, 2);

myFoo(); // Object{} 1 2

如果使用 name 屬性查看 bind() 所創建的函式,將會在函式的名稱前加上 "bound"

// 範例 3
let obj = {};

function foo() {
  console.log(this);
}

const myFoo = foo.bind(obj);

console.log(myFoo.name);  // "bound foo"

重新指向 this

立即函式 ( IIFE ) 或是非同步事件 ( setTimeout ) 大多會指向全域,如果需要調用的是物件本身,可以用一個變數指向 this,等到調用後再重新使用它。

function callName() {
  console.log('區域', this.name); // 區域 東尼史塔克
  var that = this;
  setTimeout(function () {
    console.log('全域', this.name); // 全域 索爾
    console.log('區域', that.name); // 區域 東尼史塔克
  }, 10);
}

var name = '索爾';
var ironMan = {
  name: '東尼史塔克',
  callName: callName
}

ironMan.callName();

上面我們看到一個 that,實際上這個變數名稱我們可以自己定義,常用的有 thatvmself,辦實際上還是可以照自己的習慣命名。


雖然我成功寫完了這 30 篇文章,但對我來說這其實不是結束,反而只是開始,所以自己還是希望如果之後時間可以,或許也還會以發文的方式繼續和大家一起討論 JavaScript,也希望看過文章發現錯誤的朋友能夠告知錯誤,也歡迎有新想法的朋友能一起討論,相信在討論的過程中,JavaScript 會慢慢變的越來越有趣 (⊙ꇴ⊙)

參考資料:

JavaScript 的 this 到底是誰?
JavaScript 的 this
JS 原力覺醒 Day17 - this 的四種繫結


上一篇
JavaScript Day 29. 立即函式 IIFE
下一篇
我的JavaScript日常- 第 31 天不是結束,反而是開始
系列文
我的JavaScript日常31

1 則留言

1
烏木白
iT邦新手 5 級 ‧ 2021-10-09 15:06:11

恭喜完賽~撒花!

我要留言

立即登入留言