JS-一次搞懂 JavaScript 的 this

一次搞懂 JavaScript 的 this

前言

在FB直播看到 姚偉揚 老師再說明javascript各種 this 的分辨,在此紀錄各種案例。


議程重點

  1. 一般函式的 this
  2. ES6 箭頭函式的 this
  3. Vue 組件的 this
  4. bind/apply/call 的 this

一般函式的this

this 主要是 看函式(function)怎麼執行 的,下列為常見的分辨的方法

  • 直接執行 → global
  • 作為物件的成員函式執行 → 該物件
  • 作為 DOM 事件偵聽函式 → 該 DOM
  • 作為建構函式 → 建構出來的實例

下列為各種this的判斷

範例1

直接呼叫
1
2
3
4
5
function a(){
console.log(this);
}

a(); //window

範例2

作為物件的成員函式執行
1
2
3
4
5
6
7
8
function a(){
console.log(this);
}

const obj = {};
obj.a = a;

obj.a(); //obj

範例3

1
2
3
4
5
6
7
8
9
10
const obj = {
a(){
function b(){
console.log(this);
}
b();
}
}

obj.a(); //window

因為 b() 是直接執行,並不是以 obj.b() 方式執行。

範例4

DOM
1
<button id='btn'>btn</button>
作為 DOM 事件偵聽函式
1
2
3
4
5
6
7
function a(){
console.log(this);
}

const obj = {a};

btn.addEventListener('click', obj.a); //DOM元素:<button id='btn'>btn</button>

範例5

DOM
1
<button id='btn'>btn</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
function a(){
console.log(this);
}

const obj = {
b(){
return function(){
a();
}
}
};

btn.addEventListener('click', obj.b()); //window

因為 a() 是直接執行。

承上例,執行 obj.b() 後的函式
1
2
3
4
5
6
7
function a(){
console.log(this);
}

btn.addEventListener('click', function(){
a(); //window
});

範例6

DOM
1
<button id='btn'>btn</button>
1
2
3
4
5
6
7
8
9
const obj = {
b(){
return function(){
console.log(this);
}
}
};

btn.addEventListener('click', obj.b()); //DOM元素:<button id='btn'>btn</button>
承上例,執行 obj.b() 後的函式
1
2
3
btn.addEventListener('click', function(){
console.log(this);
});

範例7

DOM
1
<button id='btn'>btn</button>
1
2
3
4
5
6
7
8
9
10
11
function a(){
console.log(this);
}

const obj = {
a(){
return a;
}
};

btn.addEventListener('click', obj.a()); //DOM元素:<button id='btn'>btn</button>
承上例,執行 obj.a() 後的函式
1
2
3
btn.addEventListener('click', function(){
console.log(this);
});
可注意觀察 範例5~7 程式碼之間有些微的不同,所造成的this有所不一樣。

善用的方式

另外一種思考的方式,我們在寫物件內的函式時,為了確保 this 能夠正確運作會先將它賦予在另一個變數上 (that, self, vm…)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var auntie = {

name: '漂亮阿姨',

callName () {

// 先使用另一個變數指向 this,讓內層函式可以正確使用
var that = this;

setTimeout(function () {
console.log(that); // auntie 這個物件
}, 10);

}

}

auntie.callName();

箭頭函式的 this

口訣:

箭頭函式裡面的this 等於 外面的this

白話文:

箭頭函式裡的this 主要是依據 外層函式(function)裡的this 是什麼就跟著是什麼。

規則

1
2
3
4
5
6
function x(){
this <= 外層函式的this,規則參考「一般函式的this
const a = () => {
this <= 依據外層函式的this
}
}

範例1

DOM
1
<button id='btn'>btn</button>
1
2
3
4
5
const a = () => {
console.log(this);
}

btn.addEventListener('click', a); //window

範例2

1
2
3
4
5
6
7
const obj = {
a: () => {
console.log(this);
}
}

obj.a(); //window

範例3

DOM
1
<button id='btn'>btn</button>
1
2
3
4
5
6
7
8
function a() {
const b = () => {
console.log(this);
}
b();
}

btn.addEventListener('click', a); //DOM元素:<button id='btn'>btn</button>

一般函式範例3 有點類似,雖然 b() 都是直接執行,
不過因為箭頭函式裡的this 主要是依據 外層函式(function)裡的this 是什麼就跟著是什麼,
所以 a()this 是 DOM元素,所以這裡的 b()this 也會跟著是 DOM元素。

範例4

Q:包了好幾層的箭頭函數的 this 也是指向 windows 嗎?

A:c() 一層撥一層 直到 function a(),再參照 a()this 是指向誰。

洋蔥式一層一層拆解
1
2
3
4
5
6
7
8
9
10
11
function a(){
//this <= 外層函式的this,規則參考「一般函式的this」

const b = () => {
const c = () => {
console.log(this); //<= 依據外層函式的this
};
c();
};
b();
};

Vue 組件的 this

全部皆指向 vue實例 本身

範例1

一般函數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var vm = new Vue({
el: '#app',
data: {
message: 'Hello'
},
computed: {
getMessage: function () {
console.log(this); // this 指向 vm 实例
}
},
methods: {
prompt: function() {
console.log(this); // this 指向 vm 实例
}
}
})

範例2

箭頭函數
1
2
3
4
5
6
var vm = new Vue({
el: "#app",
methods: {
test: () => console.log(this), //window
},
});

範例3

若是 vue的建立是包在 函數 裡面時,使用 箭頭函數 ,則this要看外層函數的this。

1
2
3
4
5
6
7
8
9
10
function init() {
//this <= 外層函式的this,規則參考「一般函式的this」

var vm = new Vue({
el: "#app",
methods: {
test: () => console.log(this), //<= 依據外層函式的this
}
});
}
如果在vue裡,寫箭頭函式要使用this的話,因為vue是物件,而不是函式
此時箭頭函式裡的this會指向外層函式的this
所以在vue要使用this的話,建議使用 一般函式 寫法。

bind/apply/call 的 this

  • 一般函式使用 bind/apply/call 時,this 就是所傳入的物件

  • 箭頭函式使用 bind/apply/call 時,this 不會有變化,規則一樣是 依據外層函式(function)裡的this 是什麼就跟著是什麼。

範例1

DOM
1
<button id='btn'>btn</button>
一般函式
1
2
3
4
5
6
7
8
9
function add(x, y){
console.log(this);
}

const b = add.bind(btn);
b(3, 5);

add.apply(btn, [3, 5]);
add.call(btn, 3, 5);

範例2

DOM
1
<button id='btn'>btn</button>
箭頭函式
1
2
3
4
5
6
7
8
9
const add = (x, y) => {
console.log(this);
}

const b = add.bind(btn);
b(3, 5);

add.apply(btn, [3, 5]);
add.call(btn, 3, 5);