第 30 天,本來想說或許最後一天可以來一篇心得文,讓自己好好休息一下,因為這 30 天花了大量的精神在一邊找資料一邊吸收知識順便應付職場,整體覺得需要出去吃喝玩樂放爛一天╰(°▽°)╯
但又突然發現,我是跟著這 30 天結束了就會停止學習了嗎?又或者是這 30 天之後我就不會再碰任何程式語言了?好像也不是,那假如不是的話,30 天之後再休息好像也是可以的 XD;所以,我們就繼續來討論個什麼吧 ˊ_>ˋ
想要理解 this,我們可以記住兩個關鍵,第一 this 永遠指向一個對象,第二 this 的指向取決於函式的調用以及呼叫方式。this 預設指向為全域性物件 window,並且 this 只能指向物件,哪個物件呼叫函式,函式裡的 this 就預設指向哪個物件。
this 的調用分成幾種:
直接調用函式的 this
會指向 window
:
window.doraemon = '哆啦A夢';
function callDoraemon() {
console.log('call:', this.doraemon);
// this == window
// 所以此時的 this.doraemon 一樣可以取得 window 下的 doraemon
}
callDoraemon(); // call: 哆啦A夢
另外這個例子是 function 包覆 function,不過這裡的 this
依然是會指向全域,因為我們是直接呼叫,使用直接調用的方式不論在哪一層的 this
都會指向全域。
window.sonOfOdin = '索爾';
function callSonOfOdin () {
console.log('call:', this.sonOfOdin); // call: 索爾
// function 內的 function
function callAgainSonOfOdin () {
console.log('call again:', this.sonOfOdin);
}
callAgainSonOfOdin(); // call again: 索爾
}
callSonOfOdin();
物件方法調用的重點在於,是在哪一個物件下被呼叫,以下範例會先給一個純粹調用的例子,然後才會是物件方法調用的範例。
// 物件方法調用
function callAvengers() {
console.log(this.avengers);
}
var avengers = 'iron Man';
var hulk = {
avengers: 'hulk',
callAvengers: callAvengers
}
callAvengers() // iron Man
hulk.callAvengers() // hulk,this 在 hulk 物件下被調用,因此指向 hulk
宣告的方法不重要,重要的是我們怎麼呼叫,如果我們將物件內的函式賦予在一個變數上並調用它。這個 this
也會指向全域。
var avengers = 'iron Man';
var hulk = {
avengers: 'hulk',
callAvengers: function () {
console.log(this.avengers);
}
}
callTheAvengers = hulk.callAvengers;
callTheAvengers() // iron Man
DOM 搭配 addEventListener
時,this
的指向則是該 DOM。這裡卡斯伯老師說:接下來點擊畫面任何一區域,該區域則會加上紅線。
var e = document.getElementsByTagName('div');
function changeDOM() {
console.log(this); // 指向當前的 DOM
this.style.border = '1px solid red'
}
for (var i = 0; i < e.length; i++) {
e[i].addEventListener('click', changeDOM, false);
}
這邊我們另外再參考一個簡單的範例,這裡不論點到哪裡都會顯示我們所點到的資訊,表示此時的 this
會指向我們點擊到的 DOM。
let e = document.querySelector('body');
function changeDOM() {
console.log(this); // <body>...</body>
}
e.addEventListener('click', changeDOM);
建構式下會 new 出一個新物件,此時的 this
會指向新的物件。
function mickeyMouse () {
this.mouse = '米奇'
}
var myFavourite = new mickeyMouse();
console.log(myFavourite.mouse); // 米奇
這裡的 this
不是全域,而且可以在新生成的物件上重新定義,所以他會指向新生成的物件。
var disney = 'Mickey Mouse';
function disneySeries(role) {
this.disney = role || 'Donald Duck';
}
var myFavourite = new disneySeries('Mickey');
var ordinary = new disneySeries();
console.log('我最喜歡', myFavourite.disney); // 我最喜歡 Mickey
console.log('覺得還好', ordinary.disney); // 覺得還好 Donald Duck
call()
和 apply()
呼叫函式,然後我們設定 this
接著傳入其他參數。
// 範例 1
let obj = {};
function foo() {
console.log(this);
}
foo(); // "Window{}"
foo.call(obj); // Object{}
foo.apply(obj); // Object{}
兩者第一個參數都是 this 值,也就是要綁定的物件,差異在於後面的參數。
// 範例 2
let obj = {};
function foo(a, b) {
console.log(this, a, b);
}
foo.call(obj, 1, 2); // Object{} 1 2
foo.apply(obj, [1, 2]); // Object{} 1 2
call()
呼叫與平常函式呼叫方法相同,apply()
則需要使用陣列包起來。
bind()
會回傳一個新的函式,當被呼叫時,會將提供的值設為 this
值。
// 範例 1
function foo() {
console.log(this.name);
}
let obj = {
name: 2,
};
let boo = foo.bind(obj);
boo(); // 2
setTimeout(boo, 100); // 2
boo.call(window); // 2
bind()
方法第一個也是 this
後面的參數則跟函式一樣。
// 範例 2
let obj = {};
function foo(a, b) {
console.log(this, a, b);
}
const myFoo = foo.bind(obj, 1, 2);
myFoo(); // Object{} 1 2
如果使用 name
屬性查看 bind()
所創建的函式,將會在函式的名稱前加上 "bound"
。
// 範例 3
let obj = {};
function foo() {
console.log(this);
}
const myFoo = foo.bind(obj);
console.log(myFoo.name); // "bound foo"
立即函式 ( IIFE ) 或是非同步事件 ( setTimeout ) 大多會指向全域,如果需要調用的是物件本身,可以用一個變數指向 this
,等到調用後再重新使用它。
function callName() {
console.log('區域', this.name); // 區域 東尼史塔克
var that = this;
setTimeout(function () {
console.log('全域', this.name); // 全域 索爾
console.log('區域', that.name); // 區域 東尼史塔克
}, 10);
}
var name = '索爾';
var ironMan = {
name: '東尼史塔克',
callName: callName
}
ironMan.callName();
上面我們看到一個 that
,實際上這個變數名稱我們可以自己定義,常用的有 that
、vm
、self
,辦實際上還是可以照自己的習慣命名。
雖然我成功寫完了這 30 篇文章,但對我來說這其實不是結束,反而只是開始,所以自己還是希望如果之後時間可以,或許也還會以發文的方式繼續和大家一起討論 JavaScript,也希望看過文章發現錯誤的朋友能夠告知錯誤,也歡迎有新想法的朋友能一起討論,相信在討論的過程中,JavaScript 會慢慢變的越來越有趣 (⊙ꇴ⊙)
參考資料:
JavaScript 的 this 到底是誰?
JavaScript 的 this
JS 原力覺醒 Day17 - this 的四種繫結