先學完物件導向,學 this 才有意義


Posted by saffran on 2021-02-25

在物件導向裡面,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 的預設值就是 undefined
  • obj.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 的部分就沒問題了!

  1. 在跟物件無關的地方去 log this,會依據「是否為嚴格模式」以及「執行環境不同」而有不同的預設值(有可能是 window 或是 undefined
  2. 在物件導向裡面去 log thisthis 就會是「自己這個 instance」
  3. 呼叫一個物件裡面的 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 的值都是一樣的

要做到這件事情,就要用 bindthis 的值給強制綁定住,範例如下:

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


#javascript







Related Posts

1731. The Number of Employees Which Report to Each Employee

1731. The Number of Employees Which Report to Each Employee

瀏覽器的事件傳遞機制

瀏覽器的事件傳遞機制

Longest Common Prefix

Longest Common Prefix


Comments