Day25 | JS 函式 This 的運作

❒ 大部分 this 的運作模式

先判斷程式碼是傳統函式或箭頭函式

  • 傳統函式中的 this 只與調用方式有關,與怎麼定義 this 無關
    • 呼叫函式時,前面有任何物件 this 就會指向它,如果 this 前沒有任何物件會指向全域。( 下方會有範例 )
    • setTimeout 為 callback function 是 simple call 的一種形式,在傳統函式寫法下 this 會指向全域,為了避免 setTimeoutthis 指向全域可使用「 箭頭函式 」或是「 指向其他變數 」。( 最後範例 8 )
  • 箭頭函式沒有自己的 this,會看外層函式的 this 指向。
  • 宣告方式不同 ( varconstlet ) ,this 也會不同,要留意。

❒ #1 一個函式中包含多少參數

1
2
3
4
5
6
var a = '全域'
function fn(params) {
console.log(params, this, window, arguments);
debugger;
}
fn(1, 2, 3);

執行 debugger ,開發者工具會跳到 Sources 區域
// 執行 debugger ,開發者工具會跳到 Sources 區域 ▲

console.log(params, this, window, arguments); → 中為運行函數本身就可以執行的參數內容

  • params 為外部傳入的參數。
    • 下方程式碼中預設參數只接收一個所以顯示 1。
  • this 目前是指向 windows ,但實際在運作時可能會有很多種不同的指向,此指向會影響在使用框架時指到哪,如果想要指向特定的元件但卻指向錯誤,就會出錯。
  • window 瀏覽器本身就存在的全域變數。
  • arguments 傳統函式會帶入的參數,為類陣列會帶入所有傳入的參數內容。
    • 程式中 fn(1, 2, 3); 有三個參數 arguments 會列出所有參數值。

❒ #2 this 的指向為何

1
2
3
4
5
6
7
8
var obj = {
name: '小明',
fn: function(params) {
console.log(params, this, window, arguments);
// debugger;
}
}
obj.fn(1 , 2 , 3);

fn: 的函式程式碼與 #1 範例相同,但卻只有 this 有所不同。

  • 1 為外部傳入的參數
  • {name: "小明", fn: ƒ}this
    開發者工具截圖

❒ #3 注意:this 的指向相當複雜,大部分情境只需要了解其中一種即可(95%)

傳統函式中的 this 只與調用方式有關,與怎麼定義 this 無關 。

大部分情境只須了解其中一種,請看下方 #4 this 的各種運用變化 | this 的判斷方式

1
2
3
4
5
6
7
8
9
//使用 const 與 let 結果會有所不同,所以變數someone 請使用 var 來設定全域變數。
var someone = '全域';
function callSomeone() {
console.log(this.someone);
}
callSomeone(); //這種呼叫方式稱 simple call

// output 全域。
// 如下方所述呼叫函式 callSomeone(); 時前方沒有任何物件,所以指向全域。

❒ #4 this 的各種運用變化 | this 的判斷方式

this 的判斷 : ( 此判斷方式以傳統函式為主,箭頭函式的判斷法請看 #5 this 陷阱 )

  • 呼叫函式時,前面有任何物件 this 就會指向它 ( 例 : #4 );
  • 呼叫函式時,如果前方沒任何物件就會指向全域 ( 例 : #3 的 simple call )。

範例 1

1
2
3
4
5
6
7
8
9
10
11
12
13
var someone = '全域';
function callSomeone() {
console.log(this.someone);
}
var obj = {
someone: '物件',
callSomeone() {
console.log(this.someone);
}
}
obj.callSomeone();

// output 物件。入上述判斷callSomeone()前方有物件 obj所以指向'物件'

呼叫函式時,前面有任何物件 this 就會指向它
// 呼叫函式時,前面有任何物件 this 就會指向它 ▲

範例 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var someone = '全域';
function callSomeone() {
console.log(this.someone);
}

var obj2 = {
someone : '物件2',
callSomeone //ES6 縮寫 ,原始為 callSomeone : callSomeone()
}
obj2.callSOmeone();



//output 物件2
  • 變數 obj2 裡的 callSomeone 指向 function callSomeone()
  • 呼叫函式時,前面有任何物件 this 就會指向它,所以 this 指向變數 obj2。

範例 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var someone = '全域';
function callSomeone() {
console.log(this.someone);
}
var wrapObj = {
someone: '外層物件',
callSomeone,
innerObj: {
someone: '內層物件',
callSomeone,
}
}
wrapObj.callSomeone();
wrapObj.innerObj.callSomeone();

  • 下方程式碼中,外層與內層的物件 callSomeone 皆使用函式 callSomeone()
  • 呼叫函式時,前面有任何物件 this 就會指向它。
    • wrapObj.callSomeone(); → 所以 this 指向 wrapObj,輸出 外層物件
    • wrapObj.innerObj.callSomeone(); → 所以 this 指向 innerObj,輸出 內層物件

範例 4

1
2
3
4
5
6
7
8
9
10
11
var someone = '全域';
function callSomeone() {
console.log(this.someone);
}
var obj3 = {
someone: `物件 3`,
fn() {
callSomeone(); //通常不會這樣去取用 this
}
}
obj3.fn();
  • this 與怎麼定義無關,只和調用方式有關,調用這個 fn() 內的 callSomeone() 函式時前方沒有看到任何物件,就屬於 simple call 會指向全域。
    • obj3.fn(); → output 全域
    • ‼️ 注意 : 通常會於物件下的方式調用 this,不會使用 simple call 方式去取得 this,結果可能會不如預期,這邊解說範例用。
      ![https://ithelp.ithome.com.tw/upload/images/20221009/20119743945158YBcK.png](https://ithelp.ithome.com.tw/upload/images/20221009/20119743945158YBcK.png)
      

範例 5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var someone = '全域';
function callSomeone() {
console.log(this.someone);
}
var obj4 = {
someone: '物件 4',
fn() {
//callback function
setTimeout(function () {
console.log(this.someone);
});
}
}
obj4.fn();
  • callback function 大部分屬於 simple call 形式,所以 this 大多會指向全域,少數會重新定義。
    所以 callback function 也盡可能不要調用 thisMDN - 回呼函式(callback function)
    • obj4.fn(); → output 全域

範例 6. 請問以下 this 的結果是甚麼 ?

1
2
3
4
5
6
7
8
9
var obj = {
data: {
myName: 'hexschool',
},
getName: function () {
console.log(this.data.myName);
}
}
obj.getName();
  • getName: function () 為傳統函式,傳統函式的 this 都會看前方呼叫它的是誰。
  • obj.getName(); 結果為 hexschool。

範例 7. 請問以下 this 的結果是甚麼 ?

1
2
3
4
5
6
7
8
9
var obj = {
data: {
myName: 'hexschool',
},
getName: () => {
console.log(this.data.myName);
}
}
obj.getName();
  • 箭頭函式沒有自己的 this。它會看外層函式作用域中的 this 指向,如果外層沒有函式則會指向全域。
    這邊全域沒東西,所以結果如下。
  • obj.getName(); 結果為 TypeError: Cannot read property 'myName' of undefined

範例 8

1
2
3
4
5
6
7
8
9
10
11
12
var myName = '小明';
var obj = {
myName: '小美',
x: function () {
var myName = '小王';
setTimeout(function () {
console.log(this.myName);
}, 0);
},
y: '2',
}
obj.x();
  • setTimeout(function (){}) 為 callback function ( 大部分屬於 simple call 形式 ),會指向全域。
    但要注意,如果 callback function 這邊改為箭頭函式指向就會改變。
  • obj.getName(); 結果為 小明。

參考資訊

  • Vue 3 實戰影音課程 2021