[JavaScriptWeird]No.73 學完物件導向後的 this (二)

前言

接續上一篇的 this ,我們將利用一種比較特別的方式來看 this 的值,以及介紹除了 call()apply() 之外還可以使用 bind() 強制綁定 this ,最後將提到 this 在箭頭函式下的特性。

用另一種角度看 this 的值

上一節提到 this 基本只有在跟物件導向扯上關係的時候才有意義。

1
2
3
4
5
6
7
8
9
use strict'  
const obj = {
a: 123,
test: function(){
console.log(this);
}
}

obj.test();

而這個例子的輸出結果 this 指向的是 obj

有沒有發現幾乎每次 this 有改變的時候都是 ooo.xxx() 之類的呼叫方式

this 的值與在哪邊呼叫函式、實際上寫在哪無關

  • 真正有關係的是,如何去呼叫它

同一個例子換個方式改寫結果就不同了

1
2
3
4
5
6
7
8
9
10
'use strict'  
const obj = {
a: 123,
test: function(){
console.log(this);
}
}

var func = obj.test;
func();

這個例子就是在說,明明是同樣的輸出結果,但只要改變了呼叫的方式, this 就不一樣了。

是不是覺得要判斷 this 值是什麼有點困難?

可以試著帶入 call() 的方式去想!

1
2
3
4
5
6
7
8
9
10
'use strict'  
const obj = {
a: 123,
test: function(){
console.log(this);
}
}

(這不是可以實際執行的 code )
obj.test(); => obj.test.call(obj)

透過帶入 call() 的方式去想將有助於理解 this 的值是什麼。

obj.test() 來說,可以想像成在 obj.test() 後面補上 call() 並且填入呼叫 test() 之前的內容,就是 this 指向的地方。

在來個例子

1
2
3
4
5
6
7
8
9
10
11
'use strict'  
const obj = {
a: 123,
inner: {
test: function(){
console.log(this);
}
}
}
(這不是可以實際執行的 code )
obj.inner.test() => obj.inner.test.call(obj.inner)

一樣使用上面提到的方式去判斷,因此可以得知結果的 this 會指向 obj.inner

所以現在就可以了解,第一個例子為什麼換個方式改寫輸出會是 undefined 了。

1
2
3
4
5
6
7
8
9
10
11
'use strict'  
const obj = {
a: 123,
test: function(){
console.log(this);
}
}
var func = obj.test;

(這不是可以實際執行的 code )
func() => func.call(undefined)

  • 因為 func 前面沒有任何東西可以放入 call() ,所以會指向 undefined

做點小練習題複習一下 this

1
2
3
4
5
6
7
8
9
10
11
function log() {  
console.log(this);
}

var a = { a: 1, log: log };
var b = { a: 2, log: log };

log(); // 全域 window
a.log(); // a

b.log.apply(a) // a

bind() 讓 this 從此乖乖的

上一節介紹的 call()apply() 主要是呼叫該函式,並指定 this 值的指向。但是 bind()並不一樣:

  • bind()回傳一個一模一樣的函式,並且將 this 值強制綁定
  • 而且沒有辦法透過 call()apply() 改變 this

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    'use strict'  
    const obj = {
    a: 123,
    test: function(){
    console.log(this);
    }
    }

    const bindTest = obj.test.bind('aaa');
    bindTest(); // aaa
    bindTest.call('qweqwe'); // aaa
  • bind() 的用法其實跟前面兩個蠻接近的,第一個參數都是指定 this

this 在箭頭函式下的特性

在介紹之前做個小練習

1
2
3
4
5
6
7
8
9
10
11
class Test{  
run(){
console.log('run this:' ,this);
setTimeout(function(){
console.log(this);
},100)
}
}

const t = new Test();
t.run();

輸出的 log 分別為:

  • Test{}
  • 全域 window 物件

但如果將 setTimeout 內的函式改成箭頭函式呢?

1
2
3
4
5
6
7
8
9
10
11
class Test{  
run(){
console.log('run this:' ,this);
setTimeout(() => {
console.log(this);
},100)
}
}

const t = new Test();
t.run();

答案就會很明顯的不一樣囉。

與 this 的特性相反

  • this 取決於函式如何被呼叫

但箭頭函式內的 this

  • 跟函式怎麼被呼叫沒有關係
  • 表現跟 scope 的行為比較類似,取決於箭頭函式寫在哪

回到例子

箭頭函式內的 this 取決於被寫在哪,來決定 this 是什麼。

以這個例子來說,因為這個方法是這樣被呼叫的 t.run() ,所以 run 函式內的 this 會指向 t ,而同樣在 run 函式中的箭頭函式 this 也會跟著變成 t

而我們也可以透過這樣來觀察

1
2
3
4
5
6
7
8
9
10
11
class Test{  
run(){
console.log('run this:' ,this);
setTimeout(() => {
console.log(this);
},100)
}
}

const t = new Test();
t.run.call(456);

當我們指定 this 後,也會連帶的影響到箭頭函式內的 this

也就是說箭頭函式內的 this 會是在被定義時那個區域的 this

1
2
3
4
5
6
7
8
9
10
function test(){  
console.log('first ', this);
let arr = \['apple', 'banana', 'lemon', 'apple', 'watermelon', 'grape'\];
let result = arr.filter((item, index, array) =>{
console.log(this);
//console.log(item, index, array);
return item;
});
}
test.call('aa');

像是這個例子來說,如果沒有使用 call() 指定 this ,這邊印出的 this 值會全部都是全域 window ,但因為現在有指定 this ,所以全部都是 ‘aa’ 。

心得

總算是把支線部分全部都讀完了,真的是對我的 JavaScript 底層的理解相當的有幫助,並且也把一些在奇怪部分 (我以為我懂但其實沒有) 的觀念釐清,像是我很喜歡模仿 JavaScript 引擎、找作用域、從 ECMA 理解 hosting 那些方式,像是偵探在找線索般,一層層的往回推,最後就可以理解為什麼會是這樣。

接下來就是把剩下的主線完成, JavaScript 的坑就算完成啦。

0%