JS 紀錄7 - this 指向判斷的方式

JavaScript this

this 是什麼?

  • this 用來 ‘傳遞’ 一個 object 的引用
  • function 呼叫方法會影響 this
  • 在同個範圍(global,function)裡面傳遞不同的東西(物件,變數)

this 判定方式

  • 判斷函數被調用時 this 指向誰,立馬看 ( ) 左邊的部分
    • 如果 ( ) 左邊是一個引用(參照),那麼函數 this 指向的就是這個引用所屬的對象
    • 否則 this 指向的就是全局對象(global)
    • this 會因執行的環境與上下文 (context) 的不同,而有不同的結果
1
2
3
4
5
6
7
8
9
var foo = {
baz: function() {
console.log(this);
}
}
foo.baz(); // foo object,() 左邊是 baz,baz 屬於 foo,所以指向 foo

var anotherBaz = foo.baz;
anotherBaz(); // window ,() 左邊是 anotherBaz,anotherBaz 屬於 window,所以這個指向 window
  • context 是什麼?

    • 函式在被呼叫執行時,所處的物件環境
  • ‘use strict’

    • 非嚴格模式下,this 的內容就會是global 物件
    • 嚴格模式下,this 的內容就會是 undefined

有什麼方法

  • 默認綁定 Global Object (Window 物件)
  • 隱式綁定 函數功能(function)
  • 顯式綁定 (call、apply、bind)
  • 建構式綁定(constructor)
  • DOM

方法有什麼效果

默認綁定 Global Object (Window 物件)

  • 不加任何的修飾符直接調用函數
  • 瀏覽器的執行環境下,global object 指的就是 Window 物件
  • NodeJS 的執行環境下,global object 指的就是 Global 物件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
console.log(this)  // Window

/*----------------------------------
function statement() {
console.log(this);
}
statement(); // Window

/*----------------------------------
let expression = function() {
console.log(this);
}
expression(); // Window

/*----------------------------------
function statement() {
'use strict'
console.log(this);
}
statement(); // undefined

隱式綁定 函數功能(function)

  • 呼叫的物件不同,所以執行的結果也會不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 回到上一層 this = widows 在指定到 a,b
let people = function(){
return this.name // this 代表的是 function 執行時所屬的物件
}

// 回到上一層 this = widows 在指定到 a
let people = function(){
return a.name
}

let a ={
name:'Jimmy',
people:people
}

let b = {
name:'Amy',
people:people
}

// 在函數中 this 的指向改變了 ,所以 this 不是靜態的
console.log(a.people()) // “Jimmy” "Jimmy"
console.log(b.people()) // “Jimmy” “Amy"
  • 物件在指向的時候是 by reference 的方式
  • window 錯誤 - 在物件方法內再建立方法(function)時,物件屬性值指向 window 並經由 scop china 找到上一層的物件屬性值 所以 再次設定的物件屬性不會等於正確設定的屬性值
  • 可由 let self = this , let that = this 來設定取代

  • 一般寫法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// f1 function 執行完後離開 execution stack ,所以在執行 f2 function 會指定到 window
let o = {
f1: function () {
console.log(this); // object
let f2 = function () {
console.log(this); // window
}();
}
}
o.f1()

/*----------------------------------
// 在第二個 function 定義 this 還是 window (function in function)
let o = {
f1: function() {
console.log(this); // object
let f2 = function() {
let self = this
console.log(self); // window 錯誤
}();
}
}
o.f1()

/*----------------------------------
// 固定變數的 this 值必須在第一個 function 定義
let o = {
f1: function() {
let self = this
console.log(self); // object
let f2 = function() {
console.log(self); // object 正確
}();
}
}
o.f1()
  • 嚴格模式 ‘use strict’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 嚴格模式 不允許使用內層 this
let o = {
f1: function () {
'use strict'
console.log(this); // object
let f2 = function () {
console.log(this); // undefined
}();
}
}
o.f1()

/*----------------------------------
// 若在 嚴格模式 使用內層 this 要在第一個 function 定義
let o = {
f1: function() {
'use strict'
let self = this
console.log(self); // object
let f2 = function() {
console.log(self); // object
}();
}
}
o.f1()

/*----------------------------------
// 使用嚴格模式在 forEach 中內層 this 不會指向外部 this,而指向 undefined
let obj ={
name:'jan',
friends:['Tarzan','Cheeta'],
loop:function(){
'use strict'
console.log(this) //object
this.friends.forEach(function(friends){
console.log(this) // undefined
console.log(this.name+' knows '+friends) // error
})
}
}
obj.loop()
  • 解法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 解法1.指定變數給 this
let obj ={
name:'jan',
friends:['Tarzan','Cheeta'],
loop:function(){
'use strict'
let self = this
console.log(self) // object
self.friends.forEach(function(friends){
console.log(self) // object
console.log(self.name+' knows '+friends)
//jan knows Tarzan
//jan knows Cheeta
})
}
}
obj.loop()

/*----------------------------------
// 解法2.用 callback function 的特性 [, thisArg] 來指定 this 值
let obj ={
name:'jan',
friends:['Tarzan','Cheeta'],
loop:function(){
'use strict'
console.log(this) // object
this.friends.forEach(function(friends){
console.log(this) // object
console.log(this.name+' knows '+friends)
//jan knows Tarzan
//jan knows Cheeta
},this)
}
}
obj.loop()

/*----------------------------------
// 解法3.用 bind 強制內層 this 指定為外層 object
var obj ={
name:'jan',
friends:['Tarzan','Cheeta'],
loop:function(){
'use strict'
console.log(this) //object
this.friends.forEach(function(friends){
console.log(this) //object
console.log(this.name+' knows '+friends)
//jan knows Tarzan
//jan knows Cheeta
}.bind(this))
}
}
obj.loop()

顯式綁定 (call、apply、bind)

  • 為什麼要用 顯示綁定?
    • callback function 中的 this 往往會改變指向,造成不是我們要的結果
    • 明確(強制)指定要執行 function 中的 this 是什麼,就不用透過另一個變數來暫存 this 的方式來獲取

function method

  • call
    • function.call (thisArg, arg1, arg2, ...)
    • 提供的 this 值與傳入參數值(arguments)傳進目標函式
  • apply
    • function.apply (thisArg, [argsArray])
    • this 值傳入外,另一個傳入參數值使用 array
  • call apply 兩者共通
    • 重新定義函數的執行環境(this)
    • 使用在 context 較常變動的場景,依照呼叫時的需要帶入不同物件作為該 function 的 this
    • thisArg 可以指定為 null,this 指向為 window
    • 呼叫的當下就立即執行
  • call apply 差異
    • 傳入參數的形式不同
1
2
3
4
5
6
7
貓吃魚,狗吃肉,有一天貓不僅想吃肉,還想吃豬肉牛肉羊肉

// function.call(thisArg, arg1, arg2, ...)
狗.吃肉.call(貓,豬肉,牛肉,羊肉)

// function.apply(thisArg, [argsArray])
狗.吃肉.apply(貓, [豬肉, 牛肉, 羊肉])
  • 以 DOM 來看 call 和 apply 可以用來重新定義函數的執行環境(this的指向)
1
2
3
4
5
6
7
8
9
10
11
12
function changeStyle(attr, value){
// 自定義屬性
this.style[attr] = value;
}
console.log(this) // window
let box = document.getElementById('box’);

window.changeStyle.call(box, "height", "200px");
console.log(box) // [object HTMLDivElement]

window.changeStyle.apply(box, ['height', '200px']);
console.log(box) // [object HTMLDivElement]
  • thisArg 可以指定為 null,this 指向為 window
1
2
3
4
5
6
7
function add(a,b){
console.log(a+b)
console.log(this)
}
add(5,3) // 8 window
add.call(null,5,3) // 8 window
add.apply(null,[5,3]) // 8 window
  • Math 中用 apply 把 array 當參數傳入直接求 array 中最大值,不用自己去遍歷求值
1
2
3
4
let arr = (6,3,2,8,9)
console.log(this) // window
let max = Math.max.call(null, arr);
console.log(max) // 9
  • bind
    • function.bind (thisArg[, arg1[, arg2[, ...]]])
      • 前者為套用 this 的物件,後者以後都是 function 的參數
    • 使用 bind 寫死要綁定的物件,可避免函式呼叫時退回到預設綁定
    • 執行 function 前,綁定指定的物件這樣 this 就會是這個物件
    • 新函式在呼叫時,建立一個新函式提供的 this 值與一連串的傳入參數值來進行呼叫,不需要參數則不要傳入即可
    • 常用在像是 callback function 這種類型,可以先綁定好 this,然後讓 function 在需要時才被呼叫
    • 不會立即執行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var obj = {
msg:'Hi!'
}
// 綁定指定的物件 this
setTimeout(function() {
console.log(this.msg); // "Hi!"
}.bind(obj), 2000);

//-------------------------
let cat = {
name: 'Hello Kitty'
};

let dog = {
name: 'Snoopy'
};
// 新函式提供的 this 值
function sayHi() {
console.log('Hello, I am ' + this.name);
}

sayHi.bind(cat)(); // "Hello, I am Hello Kitty"
sayHi.bind(dog)(); // "Hello, I am Snoopy"
  • 硬綁定(hard binding)是指使用 bind 寫死要綁定的物件,可避免函式呼叫時退回到預設綁定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
在 bind(foo, obj) 中,將 foo 的 this 強制指定為 obj,並將結果指定給 bar,
因此當執行 bar(3) 時,this.a 對 obj 查找屬性 a(找到為 2,並非退回到全域範疇)
且加上傳入的數字 3,而得到結果 b 為 5
*/

function foo(something) {
console.log(this.a, something);
return this.a + something;
}

// 簡易的綁定 this 的 helper
function bind(fn, obj) {
return function() {
return fn.apply(obj, arguments);
};
}

var obj = {
a: 2
};

// 寫死要綁定的物件
var bar = bind(foo, obj);
var b = bar(3); // 2 3
console.log(b); // 5

function currying

  • 類似預設參數
  • 將接受 n 個參數的 function,轉變成 n 個到只接受一個參數的 function 過程
  • 簡化參數的處理,基本上是一次處理一個參數,藉以提高程式的彈性和可讀性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// setTimeout function 的 this 指向為 window
setTimeout(function(){
console.log(this) // window
}, 1000);

/*----------------------------------
// 在 myobj 裡面不是指向 obj 而是 window 造成呼叫時 undefined 找不到結果
var myObj = {
id: "rettamkrad",
printId: function() {
console.log('The id is '+ this.id + ' '+ this.toString());
console.log(this) // window object object
}
};
setTimeout(myObj.printId, 100);
// "The id is undefined [object Window]”

setTimeout(myObj.printId.bind(myObj), 100); // bind 寫法
// "The id is rettamkrad [object Object]”

setTimeout(function(){myObj.printId()}, 100); // closure (IIFE)寫法
// "The id is rettamkrad [object Object]”
  • 實例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// function currying  
function add(a, b) {
return a + b;
}
let add1 = add.bind(null, 1);
console.log(add1(2)); // 3
console.log(add1(4)); // 5

/*----------------------------------
// function borrowing
// bind,call,apply 借用 person 物件裡面 fullname 這個 method
var person = {
firstname:'Jimmy',
lastname:'Wei',
fullname: function(firstname,lastname){
console.log(this)
var name = this.firstname+" "+this.lastname
return name;
}
}

// person2 屬性值傳入 person 的 fullname method
var person2 = {
firstname: 'Chien-Ming',
lastname: 'Wang'
}

// holiday 取用 person 的 fullname method
var holiday = function(location1,location2){
console.log('log: '+this.fullname()) // 取用 person method
console.log('assion: '+location1+" "+location2)
}


// this = object person
// "log: Jimmy Wei"
// "assion: taiwan japan"
// "assion: londan taiwan"
// "assion: usa taiwan"
holiday.call(person,'taiwan','japan')
holiday.apply(person,['londan','taiwan'])
holiday.bind(person,'usa','taiwan')()


// person2 跟 person 調用 method
// this = object person2
// "Chien-Ming Wang”
console.log(person.fullname.call(person2))
console.log(person.fullname.apply(person2))
console.log(person.fullname.bind(person2)())

建構式綁定(constructor 物件產生時會被呼叫的方法)

  • new constructor[([arguments])]
  • 這個新建構的物件 this 會指向新建構的物件
1
2
3
4
5
6
7
8
9
10
11
let x = 2;
function test(x) {
this.x = x || 1 // 預設參數 1
console.log(this) // object
}

var obj = new test();
var obj2 = new test(3);
console.log(x) // 2
console.log(obj.x) // 1 object
console.log(obj2.x) // 3 object

DOM

  • 元素觸發事件,this 就指向那個元素
  • DOM 調用 function 就如同物件調用 function,所以此 this 所指向的則是該 DOM
1
2
3
4
5
6
7
8
9
10
function doAlert() { 
console.log(this) // window
console.log(this.innerHTML) // undefined
}
doAlert();

let myElem = document.querySelector('#test');
myElem.onclick = doAlert;
console.log(myElem.onclick === doAlert); // true
myElem.onclick(); // I am an element object

比較優先順序

  • 顯式綁定和建構式綁定無法直接比較(會報錯)
  • 匹配的優先順序由 高至低排列
    • 建構式綁定:this 會指向 new 出來的物件
    • 顯示綁定:使用 call、apply、bind,明確指出要綁定給 this 的物件
    • 隱式綁定:當函式為物件的方法(method)時,在執行階段 this 就會被綁定至該物件
    • 預設綁定:當其他規則都不適用時,沒有使用 bind、call、apply 或不屬於任何物件的 method,就套用預設綁定,在非嚴格下,瀏覽器環境 this 的值就是預設值全域物件 window,而在嚴格模式下,this 的值就是 undefined

結論

  • this 代表的是 function 執行時所屬的物件
  • 判斷函數被調用時 this 指向誰,立馬看 ( )左邊的部分
  • function 內用嚴格模式下,默認的 this 就是 undefined
  • 可由 let self = this , let that = this 來設定變數取代 this
  • call、apply、bind 明確(強制)指定要執行 function 中的 this 是什麼,不用設定變數
  • new 綁定這個新建構的物件 this 會指向新建構的物件
  • DOM 元素觸發事件,this 就指向那個元素
執行方式 範例語法 this等於
Global this Global object (eg. window)
Global Function foo() Global object
Object Function myObject.foo() myObject
Function using call foo.call(myCall,myArg) myCall
Function using apply foo.apply(myCall,[myArgs]) myApply
Constructor Function var newObj = new foo() newObj
Evaluation eval(thing_to_eval) 等同eval層級
顯式綁定 bind call apply
適用狀況 在執行前先綁定物件做為該 function 的 this,需要時才被呼叫不會立即執行 依照呼叫時的需要帶入不同的物件作為該 function 的 this,在呼叫的當下就立即執行 與 call 相同
傳參數的方式 代入指定的物件 func.bind (thisArg[, arg1[, arg2[, ...]]]) 參數需要一個一個指定 function.call (thisArg, arg1, arg2, ...) 參數使用陣列傳入 function.apply (thisArg, [argsArray])

element.addEventListener 中使用 this & e.target & e.currentTarget 差異

  • this 總是會拿到被監聽的對象本身,也就是 element.addEventListener() 的 element
  • e.currentTarget === this
  • e.target 則是指事件被觸發時的對象,有可能不是 element 本身
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="hero">
<h1 contenteditable>🔥WOAH!</h1>
</div>

const hero = document.querySelector('.hero')
hero.addEventListener('mousemove', moveShadow)

function moveShadow (e) {
console.log('this', this) // 回傳的一定是 ".hero”
console.log('currentTarge', e.currentTarget)
// 等同於this
// <div class="hero">
// <h1 contenteditable>🔥WOAH!</h1>
// </div>

console.log(’target', e.target)
// 回傳的可能是 ".hero" 也可能是 “h1”
// <h1 contenteditable>🔥WOAH!</h1> 回傳元素裡面的內容
}
0%