關於this是一個很神奇的東西這件事情

前言

我學習 JavaScript 中令我感到最痛苦最煩惱的其中一個東西就是 this,所以這邊就針對 this 來做一下紀錄學習。

this是什麼?

this 是 JavaScript 函式內的一個關鍵字,在不同的時機點使用就有可能有不同的結果,對於學習 JavaScript 的人來講實在非常頭痛。

全域(Global)

範例1

1
2
var name = '小明';
console.log(this.name);

這時候我們可以看到 this 會去尋找 Window 下叫 name 的東西,所以 console.log(this.name); 就會顯示小明。

所以我們這邊大膽測試一下,看一下現在的 this 是什麼。

1
console.log(this);

可以發現此時的 this 代表 window。

所以可以將console.log(this.name);這樣看

因為目前的 this 所代表的就是 window,所以 this 會從全域中去找 name 才會找出小明這個答案。

範例2

function 下使用 this,那麼會影響 this 嗎?

1
2
3
4
5
6
var name = '小明';
function namefu () {
console.log(this.name);
}

namefu();

從結果來看跟範例1是沒有什麼差別,console 一樣顯示小明。

如果用圖解來看的話會更清楚,namefu(); 其實是這樣的

範例3

另外 this 在一種特別狀況下會出現 undefined

1
2
3
4
5
6
7
"use strict" 
var name = '小明';
function namefu () {
console.log(this.name);
}

namefu();

這是一個很奇妙的狀況,嚴格模式下 this 是會變成 undefined 的,而在 ES5 的說法是…

若在非嚴格模式下 this 會指向全域 (window),但在嚴格模式就會因為安全上的考量而禁止 this 指向全域,簡單來講 this 只要一指向全域 (window) 就會變成 undefined

那麼如果我們將 name 拉入 function 中會怎麼樣呢?

1
2
3
4
5
6
7
"use strict" 

function namefu () {
var name = '小明';
console.log(this.name);
}
namefu();

結果也是一樣會是 undefined,用圖片來看就會很清楚為什麼。

所以嚴格模式 ("use strict") 下,只要 this 指向是 window 那麼絕對就會是 undefined

那麼該如何解決嚴格模式下 undefined 問題

1
2
3
4
5
6
7
8
9
10
11
12
"use strict" 

var name = '小明';
function namefu () {
console.log(this.name);
}

var a = {
name: '小王',
myname: namefu,
}
a.myname;

此時可以看到 this 出現的是小王,所以可以理解成 this > a 底下去找有沒有 name

從前面總總結論來看 this 會依照執行的環境來找值,用圖片來說明就是這樣子。

this 往前一個去找 name 的屬性,而 this 前面就是 a,所以才會出現小王。

物件(Object)

範例1

1
2
3
4
5
var name = '小明';
var obj = {
x: this.name,
}
obj.x;

結果可以發現 console 可以取得小明。

範例2

那如果變成這樣呢?

1
2
3
4
5
6
7
8
9
10
11
var name = '小明';
var obj = {
x: function () {
name = '小王';
console.log(this.name);
},
y: '2'
}

obj.x();

此時會出現 undefinde,這邊一樣更圖片來表達更清楚。

因為 this 會去找 obj 中的 name,但 obj 中並沒有 name 這個屬性,所以就回傳了 undefined

但是如果是這樣寫呢?

1
2
3
4
5
6
7
8
9
10
11
var name = '小明';
var obj = {
x: function () {
console.log(this.name);
},
y: '2',
name: '小王',
}

obj.x();

這時候就會抓到小王,而不是取得小明,跟上面圖片是一樣的。

所以由此可知 this 的特性是往前一層找 name,而 obj 中現在有 name 屬性,故輸出小王而不是小明。

但是下面這個範例就不太一樣了。

1
2
3
4
5
6
7
8
9
10
11
12

var name = '小明';
var obj = {
x: function () {
name = '小王';
console.log(this.name);
},
y: '2'
}

var a = obj.x;
a();

首先在還沒執行 obj.x; 就直接將 obj.x; 賦予 a,然後再執行 a(),這時候會跑出小王,但是如果我們依照剛剛前面的想法來講用圖解這樣子是沒錯的。

那為什麼 name 會是小王呢? x 裡面有一個 name 重新賦值所導致。

範例3

如果改寫這樣子呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
var name = '小明';
var obj = {
x: function () {
console.log(this.name);
},
y: '2',
name: '小王',
}

var a = obj.x();

a;

結果一樣會是小王,因為你先執行了 x() 這個方法,所以 this 是跑完找完東西在賦予值進去a

但是有一種狀況下滿特別的,他會變成小明

1
2
3
4
5
6
7
8
9
10
11
12
var name = '小明';
var obj = {
x: function () {
console.log(this.name);
},
y: '2',
name: '小王',
}

var a = obj.x;

a();

這時候奇妙的小明就出現了。

那麼為什麼會導致這樣子呢?這是因為 obj.x,在還沒執行下就直接賦予給 a,所以變成了 a 是一個方法,就是物件傳參考觀念是一樣的,所以用圖解來看 a 就會變成這樣。

範例4

有時候物件裡面會寫得很複雜,所以如果這樣子寫呢。

1
2
3
4
5
6
7
8
9
10
11
12
13
var name = '小明';
var obj = {
x: {
name: '小虎',
myname: function(){
console.log(this.name);
}
},
y: '2',
name: '小王',
}

obj.x.myname();

就剛剛的結論來講 this 會去向上級找 name,所以 thisx 找到的 name 就是小虎。

可是如果依照剛剛的重新賦值並不優先執行呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var name = '小明';
var obj = {
x: {
name: '小虎',
myname: function(){
console.log(this.name);
}
},
y: '2',
name: '小王',
}

var a = obj.x.myname;
a();

就剛剛得到的結論來講,因為直接將 function 賦予至 a,所以實際上 a 的上級就是 window

結論

this 的使用時機真的很奇妙,會依照使用的環境來取得不同資料,基本上可以得到幾個結論心得。

1.this 特性是向上找
2.如果執行 function 的時候是在全域下執行,那麼就對得到全域的答案,反之若是在區域下執行就會得到區域的答案。
3.嚴格模式下的 this 若指向 window 就絕對會出現 undefined。

補充

前陣子在撰寫 jQuery 的時候發生一件事情,透過 this 去獲取 data-id,但卻會出現 undefined

1
2
3
4
5
6
7
8
$(document).ready(() =>{
$('.deletePost').on('click',(e) => {
e.preventDefault();
const id = $(this).data('id');
const title = $(this).data('title');
console.log(id,title);
})
});

查看了一下 this 才發現,this 指向到window了,並不是指向 .deletePost,所以才會找不到 data-id。

後來經過大大提醒 this 在甚麼樣的情況下會改變,然後提供了一篇文章給我參考,裡面講到了一句話。

Arrow function 對於它也是又愛又恨的,看似簡約的外型卻有著全新的體驗,它有著更簡短的語法以及重新定義的 this。

看到這段大概知道問題了,在箭頭函式下 this 對應呼叫方式被重新定義,所以才會導致無法抓到 data-id。

所以重點整理一下

  • 綁定的 this 不同之處
    • 傳統函式: 依呼叫的方法而定
    • 箭頭函式: 依綁定到其定義時所在的物件 <-這句話真的有看沒有懂。

為了搞懂這個觀念,所以拿前面的練習來試試看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var name = '小明';
var obj = {
x: {
name: '小虎',
myname: function(){
console.log(this.name);
}
},
y: '2',
name: '小王',
}

var a = obj.x.myname();
a;

用原本的 function 來執行 this.name 會跑出小虎無誤。

但如果改成箭頭函式呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var name = '小明';
var obj = {
x: {
name: '小虎',
myname: () => {
console.log(this.name);
}
},
y: '2',
name: '小王',
}

var a = obj.x.myname();
a;

神奇的事情發生了,小明出現!

所以由此可知在箭頭函式,原本的觀念就會被重新定義…不在是這樣子

附帶一題這個就是傳統函式:依呼叫的方法而定

所以若再箭頭函式下使用 this 就會被重新定義至window。

所以就會變成這樣

如果要確定 this 指定在 function 中就必須這樣撰寫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var name = '小明';
var obj = {
x: {
name: '小虎',
myname: function() {
console.log(this.name);
setTimeout(() => {
console.log(this.name);
},500)
}
},
y: '2',
name: '小王',
}

var a = obj.x.myname();
a;

簡單來說若要使用箭頭函式的 this,外層就要使用 function 來避免 this 指向至 window,這樣 this 才會正確指向進 function 屬性中。

所以依照這個觀念將箭頭函式改寫成 function 來查看 this 現在指向哪裡囉。

1
2
3
4
5
6
7
8
9
$(document).ready(() =>{
$('.deletePost').on('click',function(e) {
console.log(this)
e.preventDefault();
const id = $(this).data('id');
const title = $(this).data('title');
console.log(id,title);
})
});

所以就可以看到 this 正常的指向我要的 dataset 了QQ

參考

JavaScript This 系列文:this 與物件的關係

鐵人賽:箭頭函式 (Arrow functions)

关于 this 的解释说明

可以給點牡蠣。
Google AD