JavaScript 核心觀念(41)-函式以及 This 的運作-this:簡易呼叫

前言

這一篇將會接續前一章節沒有說明的「簡易呼叫」,也就是 Simple Call。

Simple Call

首先什麼是 Simple Call 呢?Simple Call 中文是簡易呼叫,基本上直接呼叫一個函式內的 this 就會形成簡易呼叫

1
2
3
4
5
function fn() {
console.log(this);
}

fn(); // window

而簡易呼叫的特色是什麼呢?通常簡易呼叫會導致 this 直接指向 Window,因此上方範例就是直接指向 Window,若將程式碼改成以下就可以看出來差異

1
2
3
4
5
function fn() {
return this;
}

fn() === window; // true

但是在實際開發時,請盡可能避免使用到 Simple Call 的 this,而 Simple Call 會發生在哪幾種狀況呢?簡單來講只要你看函式是直接執行的,並且裡面有使用到 this 那麼就會形成 Simple Call,因此這邊可以看一段課程範例

1
2
3
4
5
6
7
8
9
var myName = 'Ray';
(function() {
console.log(this.myName); // Ray
function callSomeone() {
console.log(this.myName); // Ray
}

callSomeone();
})();

在此可以看到上方範例不管怎麼樣都是指向 window,而 IIFE 都會產生 Simple Call 了,那麼閉包呢?

1
2
3
4
5
6
7
8
9
10
function fn() {
var a = 1;
return function(update) {
a = a + update;
console.log(this, this.a)
}
}

var qq = fn();
qq(10);

上方範例我們依然可以看到 this 是指向 window 更不用說因為 this 指向 window 的關係,而底下並沒有 a 這個變數而出現 undefined,而在此也是因為「直接呼叫該函式」而導致產生 Simple Call,因此若將來看到直接呼叫
函式的方式,那麼就必定會產生 Simple Call。

除此之外還有一種狀況會形成 Simple Call,也就是 Callback 的形式,例如

1
2
3
4
5
6
7
8
function fn(cb) {
var myName = 'Ray';
cb();
}

fn(function() {
console.log(this, this.myName);
})

這種狀況也產生 Simple Call,基本上必須看它是如何執行的,如果是直接執行的話,那麼必定會產生 Simple Call 導致 this 跑掉,但是如果將程式碼改成以下,那麼結果就會不同

1
2
3
4
5
6
7
8
9
10
11
function fn(cb) {
var obj = {
myName: 'Ray',
cb: cb,
};
obj.cb();
}

fn(function() {
console.log(this, this.myName);
})

因為上方的呼叫方式就變成了在物件下呼叫,因此 this 就不會指向到 window,但通常 Callback 都會形成 Simple Call 尤其是 JavaScript 本身內建的方法都常都會形成這個問題,例如先前的範例就是一個狀況

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var myName = 'JavaScript'
function fn () {
setTimeout(function() {
console.log(this.myName);
}, 1000);
}
var data = {
myName: 'Ray',
callName: fn,
ming: {
myName: 'Ray2',
callName: fn,
}
}

data.callName();

除此之外 ES6 的 forEach 也是一個 Callback

1
2
3
4
5
6
var arr = [1, 2, 3];
var myName = 'Ray1';
arr.forEach(function() {
var myName = 'Ray2';
console.log(this.myName);
})

因此這邊基本上只要看到傳入的是一個函式又傳入另一個函式,基本上就有很高的機會變成 Callback 而形成 Simple Call。

最後拉回剛剛的一個範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var myName = 'JavaScript'
function fn () {
setTimeout(function() {
console.log(this.myName);
}, 1000);
}
var data = {
myName: 'Ray',
callName: fn,
ming: {
myName: 'Ray2',
callName: fn,
}
}

data.callName(); // JavaScript

上面範例我們知道 this 會跑掉,那麼如果想避免 this 跑掉的話,該怎麼做呢?基本上常見的做法就是在 CallBack 外層宣告一個變數儲存 this,通常名稱會是 vm 或是 self

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var myName = 'JavaScript'
function fn () {
const vm = this;
setTimeout(function() {
console.log(vm.myName);
}, 1000);
}
var data = {
myName: 'Ray',
callName: fn,
ming: {
myName: 'Ray2',
callName: fn,
}
}

data.callName(); // Ray

而上方寫法基本上與下方相同只是程式碼撰寫方式放的方式不同而已

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var myName = 'JavaScript'

var data = {
myName: 'Ray',
callName: function() {
const vm = this;
setTimeout(function() {
console.log(vm.myName);
}, 1000);
},
ming: {
myName: 'Ray2',
callName: function() {
const vm = this;
setTimeout(function() {
console.log(vm.myName);
}, 1000);
},
}
}

data.callName(); // Ray

但通常來講其實我不太建議將變數命名為 self,因為 self 其實就是 window 本身,在此可以看這邊的討論串,以下擷取重點

You should avoid self as there is a window.self object and you could end up using that accidentally if you forget to declare your own self var (e.g. when moving some code around). This can be annoying to spot/debug. Better to use something like _this。

參考文獻

Liker 讚賞 (拍手)

如果這一篇筆記文章對你有幫助,希望可以求點支持或 牡蠣 鼓勵 (ノД`)・゜・。

Liker 是一個按讚(拍手)的讚賞機制,每一篇文章最多可以按五下(拍手),按讚過程你是完全不用付費的(除非你想要每個月贊助我 :D),你只需要登入帳號就可以開始按讚。
而 Liker 會依據按讚數量分配獎金給創作者,所以如果你願意按個讚我會非常感謝你唷。

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ