在物件導向裡面,this 的意義在哪
this
原本只是用來在物件導向裡面使用的
在物件導向裡面,this
是用來存取「目前對應到的這個 instance」,不然也沒有其他方法可以去存取目前對應到的 instance 了
this
對應到的就是「目前在呼叫 function 的這個 instance」
在沒有意義的地方呼叫 this,預設值會是什麼?
如果不是在物件導向的環境(也沒有在嚴格模式下),this
的預設值會是一個「global 的東西」,global 會取決於環境而有不同的值,但都會是一個全域的東西
例如:
在 Node.js 執行時,this
的預設值是一個叫做 global
的變數
function test() {
console.log(this === global); // 在 Node.js 裡面,this 的預設值是一個叫做 global 的變數
}
test(); // true
在瀏覽器執行時,this
的預設值是 window
use strict
開啟嚴格模式
但其實,會有上面這些預設值還滿奇怪的,因為 this
並沒有指向到任何東西,怎麼還會給它這些 global 的值呢?
這時,可以用 "use strict"
來開啟嚴格模式
在嚴格模式下,不管是在 Node.js 或是瀏覽器,this
預設值都會是 undefined,因為 this
本來就不需要任何預設值
Node.js
"use strict"; // 開啟嚴格模式
function test() {
console.log(this);
}
test(); // undefined
瀏覽器
把 this
放在 function 裡面,this
預設值也是 undefined
"use strict"; // 開啟嚴格模式
function test() {
var a = 1;
function inner() {
console.log(this);
}
inner();
}
test(); // undefined
用 DOM + 瀏覽器的事件
如果是「用 DOM + 瀏覽器的事件」,this
的預設值就是「你去實際做操作的那個東西」
例如以下的 EventListener:
我 click 到哪一個按鈕,this
就會指向到那個按鈕
//用 DOM + 瀏覽器的事件
document.querySelector("btn").addEventListener("click", function () {
console.log(this); // 我 click 到哪一個按鈕,this 就會指向到那個按鈕
});
如果不是在物件導向的環境,也不是「用 DOM + 瀏覽器的事件」,基本上 this
都是沒有任何意義的(undefined)
另外兩種呼叫 function 的方法:call 與 apply
用 call()
來呼叫 function
在 call()
裡面傳入的第一個參數,就會是 function 裡面的 this
"use strict"; // 開啟嚴格模式
function test() {
console.log(this); // 123
}
test.call(123);
用 apply()
來呼叫 function
在 apply()
裡面傳入的第一個參數,就會是 function 裡面的 this
"use strict"; // 開啟嚴格模式
function test() {
console.log(this); // [ 1 ]
}
test.apply([1]);
call 跟 apply 的差別
call 跟 apply 的差別就只有一個,那就是:
call()
可以傳多個參數,都是用逗號隔開apply()
的參數只能有兩個,第二個參數會是一個 array,用 array 把我要傳的參數包起來
call 跟 apply 共同的重點就是:
call()
跟 apply()
的第一個參數,都可以去改變 function 裡面 this
的值。傳入什麼值,this
就會是什麼值
call
對於 call 來說,可以傳多個參數,都是用逗號隔開
- 第一個參數
"hello"
就會是this
的值 - 然後 1, 2, 3 就會依序對應到參數 a, b, c
"use strict"; // 開啟嚴格模式
function test(a, b, c) {
console.log(this); // [String: 'hello']
console.log(a, b, c); // 1 2 3
}
test.call("hello", 1, 2, 3);
apply
對於 apply 來說,參數只能有兩個
- 第一個參數是
this
的值 - 第二個參數會是一個 array,用 array 把我要傳的參數包起來(用 array 的方式來表示參數)
"use strict"; // 開啟嚴格模式
function test(a, b, c) {
console.log(this); // hello
console.log(a, b, c); // 1 2 3
}
test.apply("hello", [1, 2, 3]); // 第二個參數會是一個 array,用 array 把我要傳的參數包起來
用另一種角度來看 this 的值
this
的值跟「寫在程式碼的哪裡」無關,只跟「function 是怎麼被呼叫的」有關
範例一:this
的值會是「obj
本身」
"use strict"; // 開啟嚴格模式
const obj = {
a: 123,
test: function () {
console.log(this); // { a: 123, test: [Function: test] }
},
};
obj.test();
範例二:this
的值都會是 inner
這個物件
「直接呼叫 function」跟「用 call 呼叫」,this
的值都會是 inner
這個物件
"use strict"; // 開啟嚴格模式
const obj = {
a: 123,
inner: {
test: function () {
console.log(this); //
},
},
};
obj.inner.test(); // { test: [Function: test] }
obj.inner.test.call(obj.inner); // { test: [Function: test] }
範例三:this
的值會是 undefined
執行 func()
會印出 this
的值是 undefined
"use strict"; // 開啟嚴格模式
const obj = {
a: 123,
inner: {
test: function () {
console.log(this); // undefined
},
},
};
const func = obj.inner.test;
func();
原因為:
func()
可以看成是 func.call(undefined)
因為在 func.call()
前面沒有其他東西了,所以第一個參數就只能是 undefined
"use strict"; // 開啟嚴格模式
const obj = {
a: 123,
inner: {
test: function () {
console.log(this); // undefined
},
},
};
const func = obj.inner.test;
func() 可以看成是 func.call(undefined);
小技巧:把 function call 轉成用 call()
的形式來呼叫,就可以很清楚知道 this
的值會是什麼了
用 call()
這個形式來呼叫 function,就可以很簡便的得知 this
的值
例如:
func()
因為在func
前面沒有任何東西,所以this
的預設值就是 undefinedobj.inner.test()
因為在test()
前面是obj.inner
,所以this
的預設值就是obj.inner
func() 可以看成是 func.call(undefined); // this 的預設值就是 undefined
obj.inner.test() 可以看成是 obj.inner.test.call(obj.inner) // this 的預設值就是 obj.inner
"use strict"; // 開啟嚴格模式
const obj = {
a: 123,
inner: {
test: function () {
console.log(this); // undefined
},
},
};
const func = obj.inner.test;
func() 可以看成是 func.call(undefined);
obj.inner.test() 可以看成是 obj.inner.test.call(obj.inner)
只要掌握幾個重點,this
的部分就沒問題了!
- 在跟物件無關的地方去 log
this
,會依據「是否為嚴格模式」以及「執行環境不同」而有不同的預設值(有可能是window
或是undefined
) - 在物件導向裡面去 log
this
,this
就會是「自己這個 instance」 - 呼叫一個物件裡面的
this
,可以把 function call 轉換成call()
的形式,call()
的第一個參數是什麼,this
的預設值就會是什麼
this
的練習題:
下面各會印出什麼值呢?
"use strict";
function log() {
console.log(this);
}
var a = { a: 1, log: log };
var b = { a: 2, log: log };
log(); // undefined
a.log(); // { a: 1, log: [Function: log] }
b.log.apply(a); // { a: 1, log: [Function: log] }
- 第 10 行
log()
就是在一個沒有意義的地方呼叫this
,在嚴格模式下,this
的值就會是 undefined - 第 11 行
a.log()
,this
的值就會是a
- 第 12 行
b.log.apply(a)
,在apply()
裡面傳入的第一個參數就會是this
的值,所以就是a
用 bind
強制綁定 this
的值
下面範例中,用不同的方式去呼叫 function,this
就會有不同的值:
- 第 10 行
obj.test()
,this
的值是obj
- 第 12 行
func()
,this
的值是 undefined
"use strict";
const obj = {
a: 1,
test: function () {
console.log(this);
},
};
obj.test(); // this 的值是 obj
const func = obj.test;
func(); // this 的值是 undefined
但我想要做的是:不管用什麼方式呼叫 function,this
的值都是一樣的
要做到這件事情,就要用 bind
把 this
的值給強制綁定住,範例如下:
把 bind()
小括號裡面的東西當作 this
放到 obj.test
綁定完成之後,我就不用擔心因為呼叫的方式不同而有不同的 this
值了
就算是用 bindTest.call(123)
來呼叫 function,this
的值依然會是 hello
bind
跟「call
, apply
」不同的地方在於:
bind
幫我把this
綁定完之後,會回傳一個 function,所以bindTest
會是一個 function- 「
call
,apply
」是幫我指定完this
的值之後,會直接呼叫 function
"use strict";
const obj = {
a: 1,
test: function () {
console.log(this);
},
};
const bindTest = obj.test.bind("hello");
bindTest(); // this 的值會是 hello
bindTest.call(123); // 就算用 call 來呼叫,this 的值依然會是 hello
arrow function 的 this
先看這個沒有箭頭函式的範例:
class Test {
run() {
console.log("run this: ", this); // this 的值會是 t 這個 instance
setTimeout(function () {
console.log(this); // this 的值會是 window
}, 100);
}
}
const t = new Test();
t.run();
把上面的程式碼貼到瀏覽器的 console 去執行:
- 第 3 行的
this
就會是t
這個 instance - 第 5 行的
this
就會是 window
為什麼第 5 行的 this
會是 window 呢?
因為過了 100 毫秒之後執行 setTimeout
裡面的 function,就是在一個沒有意義的地方呼叫 this
,所以在寬鬆模式下,瀏覽器的 this
預設值就會是 window(如果是在嚴格模式下,瀏覽器的 this
預設值就會是 undefined)
箭頭函式的 this
現在,我把 setTimeout
裡面的 function 改成箭頭函式,結果印出來的 this
跟上一層的 this
是一樣的(都是 t
這個 instance)
class Test {
run() {
console.log("run this: ", this); // this 的值會是 t 這個 instance
setTimeout(() => {
console.log(this); // this 的值會是 t 這個 instance
}, 100);
}
}
const t = new Test();
t.run();
原因為:
這是箭頭函式的特性(是一個特例)
箭頭函式裡面的 this
,跟我怎麼呼叫沒有關係,而是跟「定義在程式碼的哪裡」有關(概念跟 scope 比較像)
上一層定義好的 this
是什麼,在箭頭函式裡面的 this
就會是什麼
箭頭函式會去用「一開始被定義好的 this
的值」
因為是在 run
裡面定義了這個箭頭函式,所以在箭頭函式裡面的 this
就會是「在 run
裡面我可以存取的到的這個 this
」