參考網站
參考網站
參考網站
參考網站
參考網站
在 JavaScript 中,「this 是什麼」絕對是讓人頭痛難題前三名。This 和物件方法息息相關,因此這篇文章會先介紹在物件方法、物件方法中的 this 是如何被決定的,把握一個簡單原則就可以知道 this 到底是誰,最後統整各個 this 的指向 😁
講解 this 的文章已經超級無敵多了,而且每一篇都寫得很不錯,之前看完 What’s THIS in JavaScript? 系列之後覺得講解的很完整,若是沒有把握自己能夠講得更清楚或是以不同的角度切入,似乎就沒必要再寫一篇文章;
若是想要「完全」搞懂 this,要付出的成本可能比你想像中要大得多。
這裡所說的「完全」指的是無論在任何情況下,你都有辦法講出為什麼 this 的值是這樣,直接給大家一個範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
}
}
//範例1
console.log(foo.bar());
//範例2
console.log((foo.bar)());
//範例3
console.log((foo.bar = foo.bar)());
//範例4
console.log((false || foo.bar)());
//範例5
console.log((foo.bar, foo.bar)());
|
你能答的出來嗎?如果不行的話,代表你沒有「完全」懂 this。要完全懂 this 之所以要付出的成本很大,就是因為「完全懂 this」指的就是「熟記 ECAMScript 規範」。this 的值是什麼不是我們憑空想像的,其實背後都有完整的定義,而那個定義就是所謂的 ECMAScript 規範,你必須先搞懂這個規範,才有可能完全理解 this 在每個情況下所指涉的對象。
若是你真的很想完全搞懂,推薦你這一篇:JavaScript 深入之从 ECMAScript 规范解读 this,我上面的範例就是從這一篇拿來的,想看解答、想理解為什麼的話可以去看這一篇。
本篇教的方法,不會讓你把 this 完全搞懂,上面那五個範例你可能會答錯,但基本的題目你依舊可以解的出來,此文的目的是希望提供一個不同的角度來看 this,從為什麼會有 this 下手,再用一套規則來解釋 this 的值,至少讓你不再對 this 有誤解,也會知道一些常見的情境底下 this 到底是什麼。
從物件導向開始談 this
若是你對 JavaScript 的物件導向完全沒概念,你可以先補完相關基礎,並且看完這篇:該來理解 JavaScript 的原型鍊了)
如果你有寫過其他程式語言,你就知道 this 從來都不是一件什麼困難的事。它代表的就是在物件導向裡面,那個 instance 本身。
舉個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
| class Car {
setName(name) {
this.name = name
}
getName() {
return this.name
}
}
const myCar = new Car()
myCar.setName('hello')
console.log(myCar.getName()) // hello
|
在上面我們宣告了一個 class Car
,寫了 setName 跟 getName 兩個方法,在裡面用 this.name
來取放這個 instance 的屬性。
為什麼要這樣寫?因為這是唯一的方法,不然你要把 name 這個屬性存在哪裡?沒有其他地方讓你存了。所以 this 的作用在這裡是顯而易見的,所指到的對象就是那個 instance 本身
。
以上面的範例來說,myCar.setName('hello')
,所以 this 就會是 myCar
。在物件導向的世界裡面,this 的作用就是這麼單純。
或者換句話說:
一但脫離了物件導向,其實 this 就沒有什麼太大的意義
假設今天 this 只能在 class 裡面使用,那應該就不會有任何問題對吧?你有看過其他程式語言像是寫 Java 或是 C++ 的人在抱怨說 this 很難懂嗎?沒有,因為 this 的作用很單純。
那問題是什麼?問題就是在 JavaScript 裡面,你在任何地方都可以存取到 this。所以在 JavaScript 裡的 this 跟其他程式語言慣用的那個 this 有了差異,這就是為什麼 this 難懂的原因。
儘管 this 的定義不太一樣,但我認為本質上還是很類似的。要理解 this 的第一步就是告訴自己:「一但脫離了物件,就不太需要關注 this 的值,因為沒什麼意義」
沒什麼太大意義的 this
1
2
3
4
5
| function hello(){
console.log(this)
}
hello()
|
this 的值會是什麼?
延續我們前面所講的,在這種情況下我會跟你說 this 沒有任何意義,而且你千萬不要想成 this 會指到 hello 這個 function,沒有這種事。
只要記得我前面跟你說的:「脫離了物件,this 的值就沒什麼意義」。
在這種很沒意義的情況下,this 的值在瀏覽器底下就會是 window,在 node.js 底下會是 global,如果是在嚴格模式(strict),this 的值就會是 undefined。
這個規則應該滿好記的,幫大家重新整理一下:
- 嚴格模式(strict)底下就都是 undefined
- 非嚴格模式,瀏覽器底下是 window
- 非嚴格模式,node.js 底下是 global
這個就是你在其他文章看到的「預設綁定」,但我在這篇不打算用任何專有名詞去談 this。我認為不用這些名詞也不會妨礙你的理解,甚至還有可能讓你更好理解。我也不是說專有名詞不重要,是說可以先把概念學起來,再回過頭來補專有名詞。
一但脫離了物件,this 的值就沒什麼意義,在沒意義的情況底下就會有個預設值,而預設值也很好記,嚴格模式就是 undefined,非嚴格模式底下就是全域物件。
更改 this 的值
僅管 this 可能有預設的值,但我們可以透過一些方法來改它。這改的方法也很簡單,一共有三種。
前兩種超級類似,叫做 call
跟 apply
,這兩種都是能夠呼叫 fucntion 的函式,我舉一個例子給你看比較好懂:
1
2
3
4
5
6
7
8
| 'use strict';
function hello(a, b){
console.log(this, a, b)
}
hello(1, 2) // undefined 1 2
hello.call(undefined, 1, 2) // undefined 1 2
hello.apply(undefined, [1, 2]) // undefined 1 2
|
我們有一個叫做 hello 的函式,會 console.log 出 this 的值以及兩個參數。在我們呼叫 hello(1, 2)
的時候,因為是嚴格模式所以 this 是 undefined
,而 a 跟 b 就是 1 跟 2。
當我們呼叫 hello.call(undefined, 1, 2)
的時候,我們先忽略第一個參數不談,你可以發現他其實跟 hello(1, 2)
是一樣的。
而 apply 的差別只在於他要傳進去的參數是一個 array,所以上面這三種呼叫 function 的方式是等價的,一模一樣。除了直接呼叫 function 以外,你也可以用 call 或是 apply 去呼叫,差別在於傳參數的方式不同。
call 跟 apply 的差別就是這麼簡單,一個跟平常呼叫 function 一樣,一個用 array 包起來。
那我們剛剛忽略的第一個參數到底是什麼呢?
你可能已經猜到了,就是 this 的值!
1
2
3
4
5
6
7
| 'use strict';
function hello(a, b){
console.log(this, a, b)
}
hello.call('yo', 1, 2) // yo 1 2
hello.apply('hihihi', [1, 2]) // hihihi 1 2
|
就是如此簡單,你第一個參數傳什麼,裡面 this 的值就會是什麼。儘管原本已經有 this,也依然會被這種方法給覆蓋掉:
1
2
3
4
5
6
7
8
9
| class Car {
hello() {
console.log(this)
}
}
const myCar = new Car()
myCar.hello() // myCar instance
myCar.hello.call('yaaaa') // yaaaa
|
原本 this 的值應該要是 myCar 這個 instance,可是卻被我們在使用 call 時傳進去的參數給覆蓋掉了。
除了以上兩種以外,還有最後一種可以改變 this 的方法:bind
。
1
2
3
4
5
6
7
| 'use strict';
function hello() {
console.log(this)
}
const myHello = hello.bind('my')
myHello() // my
|
bind 會回傳一個新的 function,在這邊我們把 hello 這個 function 用 my 來綁定,所以最後呼叫 myHello() 時會輸出 my
。
以上就是三種可以改變 this 的值的方法。你可能會好奇如果我們把 call 跟 bind 同時用會怎樣:
1
2
3
4
5
6
7
| 'use strict';
function hello() {
console.log(this)
}
const myHello = hello.bind('my')
myHello.call('call') // my
|
答案是不會改變,一但 bind 了以後值就不會改變了。
這邊還要特別提醒的一點是在非嚴格模式底下,無論是用 call、apply 還是 bind,你傳進去的如果是 primitive 都會被轉成 object,舉例來說:
1
2
3
4
5
6
7
| function hello() {
console.log(this)
}
hello.call(123) // [Number: 123]
const myHello = hello.bind('my')
myHello() // [String: 'my']
|
詳細可以參考我另一篇文章 - 「使用 bind、call、apply 改變 this 指向的對象」,幫大家做個中場總結:
- 在物件以外的 this 基本上沒有任何意義,硬要輸出的話會給個預設值
- 可以用 call、apply 與 bind 改變 this 的值
物件中的 this
最前面我們示範了在物件導向 class 裡面的 this,但在 JavaScript 裡面還有另外一種方式也是物件:
1
2
3
4
5
6
7
8
| const obj = {
value: 1,
hello: function() {
console.log(this.value)
}
}
obj.hello() // 1
|
這種跟一開始的物件導向範例不太一樣,這個範例是直接創造了一個物件而沒有透過 class,所以你也不會看到 new 這個關鍵字的存在。
再繼續往下講之前,要大家先記住一件事情:
this 的值跟作用域跟程式碼的位置在哪裡完全無關,只跟「你如何呼叫」有關
這個機制恰巧跟作用域相反,不確定我在說什麼的可以先看這篇:所有的函式都是閉包:談 JS 中的作用域與 Closure。
舉個簡單的例子來幫大家複習一下作用域:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| var a = 10
function test(){
console.log(a)
}
const obj = {
a: 'ojb',
hello: function() {
test() // 10
},
hello2: function() {
var a = 200
test() // 10
}
}
test() // 10
obj.hello() // 10
obj.hello2() // 10
|
無論我在哪裡,無論我怎麼呼叫 test 這個 function,他印出來的 a 永遠都會是全域變數的那個 a,因為作用域就是這樣運作,test 在自己的作用域裡面找不到 a 於是往上一層找,而上一層就是 global scope
,這跟你在哪裡呼叫 test 一點關係都沒有。test 這個 function 在「定義」的時候就把 scope 給決定好了。
但 this 卻是完全相反,this 的值會根據你怎麼呼叫它而變得不一樣,還記得我們剛講過的 call、apply 跟 bind 嗎?這就是其中一個範例,你可以用不同的方式去呼叫 function,讓 this 的值變得不同。
所以你要很清楚知道這是兩種完全不同的運行模式,一個是靜態(作用域)、一個是動態(this)。要看作用域,就看這個函式在程式碼的「哪裡」;要看 this,就看這個函式「怎麽」被呼叫。
舉一個最常見的範例:
1
2
3
4
5
6
7
8
9
10
| const obj = {
value: 1,
hello: function() {
console.log(this.value)
}
}
obj.hello() // 1
const hey = obj.hello
hey() // undefined
|
明明就是同一個函式,怎麼第一次呼叫時 this.value 是 1,第二次呼叫時就變成 undefined 了?
記住我剛說的話:「要看 this,就看這個函式『怎麽』被呼叫」。
再繼續往下講之前,先教大家一個最重要的小撇步,是我從 this 的值到底是什么?一次说清楚學來的,是一個很方便的方法。
其實我們可以把所有的 function call,都轉成利用 call 的形式來看,以上面那個例子來說,會是這樣:
1
2
3
4
5
6
7
8
9
10
11
12
| const obj = {
value: 1,
hello: function() {
console.log(this.value)
}
}
obj.hello() // 1
obj.hello.call(obj) // 轉成 call -> 1
const hey = obj.hello
hey() // undefined
hey.call() // 轉成 call -> undefined
|
而規則就是你在呼叫 function 以前是什麼東西,你就把它放到後面去。所以 obj.hello()
就變成了 obj.hello.call(obj)
,hey()
前面沒有東西,所以就變成了hey.call()
。
轉成這樣子的形式之後,還記得 call 的第一個參數就是 this 嗎?所以你就能立刻知道 this 的值是什麼了!
舉一個更複雜的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| const obj = {
value: 1,
hello: function() {
console.log(this.value)
},
inner: {
value: 2,
hello: function() {
console.log(this.value)
}
}
}
const obj2 = obj.inner
const hello = obj.inner.hello
obj.inner.hello(obj.inner)
obj2.hello(obj2)
hello()
|
你可以不要往下拉,先想一下那三個 function 會各自印出什麼值。
接著我要公布解答了,只要轉成我們上面講的那種形式就好:
.
.
.
.
.
.
.
.
.
.
1
2
3
| obj.inner.hello() // obj.inner.hello.call(obj.inner) => 2
obj2.hello() // obj2.hello.call(obj2) => 2
hello() // hello.call() => undefined
|
特別講一下最後一個 hello 因為沒有傳東西進去,所以是預設綁定,在非嚴格模式底下是 window,所以會 log 出 window.value
也就是 undefined。
只要你把 function 的呼叫轉成用 call 的這種形式,就很容易看出來 this 的值是什麼。
這也是我前面一直在提的:「要看 this,就看這個函式『怎麽』被呼叫」,而你要看怎麼被呼叫的話,就轉成 call 的形式就行了。
學到這邊,其實你看見九成與 this 相關的題目你都會解了,不信的話我們來試試看(為了可讀性沒有防雷空行,所以請自行拉到程式碼就好,再往下拉就會是解答了):
1
2
3
4
5
6
7
8
9
| function hello() {
console.log(this)
}
var a = { value: 1, hello }
var b = { value: 2, hello }
hello()
a.hello()
b.hello.apply(a)
|
只要按照我們之前說的,用 call 來轉換一下形式就好:
1
2
3
| hello() // hello.call() => window(瀏覽器非嚴格模式)
a.hello() // a.hello.call(a) => a
b.hello.apply(a) => 直接用 apply,所以就是 a
|
再來一題比較不一樣的,要看仔細囉(假設在瀏覽器底下跑,非嚴格模式):
1
2
3
4
5
6
7
8
9
10
11
12
| var x = 10
var obj = {
x: 20,
fn: function() {
var test = function() {
console.log(this.x)
}
test()
}
}
obj.fn()
|
這題的話如果你搞錯,一定是你忘記了我們最重要的一句話:
要看 this,就看這個函式「怎麽」被呼叫
我們怎麼呼叫 test 的? test()
所以就是 test.call()
就是預設綁定,this 的值就會是 window
,所以 this.x
會是 10,因為在第一行宣告了一個全域變數 x = 10。
寫到這裡,再來幫大家做個回顧,避免大家忘記前面在講什麼:
- 脫離物件的 this 基本上沒有任何意義
- 沒有意義的 this 會根據嚴格模式以及環境給一個預設值
- 嚴格模式底下預設就是 undefined,非嚴格模式在瀏覽器底下預設值是 window
- 可以用 call、apply 與 bind 改變 this 的值
- 要看 this,就看這個函式「怎麽」被呼叫
- 可以把 a.b.c.hello() 看成 a.b.c.hello.call(a.b.c),以此類推,就能輕鬆找出 this 的值
不合群的箭頭函式
原本有關 this 的部分應該講到上面就要結束了,但 ES6 新增的箭頭函式卻有不太一樣的運作方式。它本身並沒有 this,所以「在宣告它的地方的 this 是什麼,它的 this 就是什麼」,好,我知道這聽起來超難懂,我們來看個範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| const obj = {
x: 1,
hello: function(){
// 這邊印出來的 this 是什麼,test 的 this 就是什麼
// 就是我說的: 在宣告它的地方的 this 是什麼,test 的 this 就是什麼
console.log(this)
const test = () => {
console.log(this.x)
}
test()
}
}
obj.hello() // 1
const hello = obj.hello
hello() // undefined
|
我們在 hello 這個 function 裡面宣告了 test 這個箭頭函式,所以 hello 的 this 是什麼,test 的 this 就是什麼。
所以當我們呼叫 obj.hello()
時,test 的 this 就會是 obj;hello()
的時候 test 的 this 就會是全域物件。這規則其實都跟之前一樣,差別只有在於說箭頭函式的 this 不是自己決定的,而是取決於在宣告時那個地方的 this。
如果你想看更複雜的範例,可以參考這篇:鐵人賽:箭頭函式 (Arrow functions)。
實際應用:React
你有寫過 React 的話,就會知道裡面其實有些概念今天的教學可以派上用場,舉例來說,我們必須在 constructor 裡面先把一些 method 給 bind 好,你有想過是為什麼嗎?
先來看看如果沒有 bind 的話會發生什麼事:
1
2
3
4
5
6
7
8
| class App extends React.Component {
onClick() {
console.log(this, 'click')
}
render() {
return <button onClick={this.onClick}>click</button>
}
}
|
最後 log 出來的值會是 undefined,為什麼?這細節就要看 React 的原始碼了,只有 React 知道實際上在 call 我們傳下去的 onClick 函式時是怎麼呼叫的。
所以為什麼要 bind?為了確保我們在 onClick
裡面拿到的 this 永遠都是這個 instance 本身。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| class App extends React.Component {
constructor() {
super()
// 所以當你把 this.onClick 傳下去時,就已經綁定好了 this
// 而這邊的 this 就是這個 component
this.onClick = this.onClick.bind(this)
}
onClick() {
console.log(this, 'click')
}
render() {
return <button onClick={this.onClick}>click</button>
}
}
|
還有另外一種方式是用箭頭函式:
1
2
3
4
5
6
7
| class App extends React.Component {
render() {
return <button onClick={() => {
console.log(this)
}}>click</button>
}
}
|
為什麼箭頭函式也可以?因為我們前面提過,「在宣告它的地方的 this 是什麼,它的 this 就是什麼」,所以這邊 log 出來的 this 就會是 render 這個 function 的 this,而 render 的 this 就是這個 component。
如果你有點忘記了,可以把文章拉到最上面去,因為最上面我們就已經提過這些了。
總結
默認綁定的規則
1
2
3
4
5
| console.log(this) // window, strict 模式下返回 undefined
function test() {
console.log(this); // window
}
test(); // 純粹的調用 (Simple call) ==> window.test() 指向 window
|
隱式綁定規則 (誰呼叫就指向誰)
1
2
3
4
5
6
7
8
9
| let a = 0;
let obj = {
a:2,
foo: function(){
console.log(this); // obj => obj 呼叫了 foo,因此 foo 裡的 this 指向為 obj
}
}
obj.foo()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| let a = 0;
let obj = {
a:2,
foo: function(){
console.log(this); // 返回 obj
function test(){
console.log(this) // window
}
test() // obj 呼叫了 foo, 但 test 並不是由 obj呼叫的,而是純粹調用,因此 this 指向 window
}
}
obj.foo()
|
IIFE(立即執行函式)
1
2
3
4
5
6
7
8
9
10
| let a = 0;
let obj = {
a:2,
foo: function(){
(function(){
console.log(this); // window
})(); // 立即執行函式(IIFE)都屬於純粹調用,所以 this 的指向都是 window
}
}
obj.foo()
|
閉包
1
2
3
4
5
6
7
8
9
10
11
12
| let a = 0;
let obj = {
a: 2,
foo: function () {
function test() {
console.log(this) // window
}
return test
}
}
obj.foo()() // obj.foo() 的返回值為 test,因此該表達式 == test() 屬於純粹調用,因此 this 指向 window
|
變數賦值的情況
1
2
3
4
5
6
7
8
9
10
11
12
13
| let a = 0;
function foo(){
console.log(this);
}
let obj = {
a:2,
foo // ES6簡寫
}
obj.foo() // obj
let bar = obj.foo // obj.foo == foo,該表達式的結果是把 foo 的引用地址賦值給了 bar
bar() // 因此 bar == foo ,屬於純粹調用,this 的指向為 window
|
參數賦值的情況
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| let a = 0;
function foo(){
console.log(this);
}
function bar(fn){
// 將傳進來的 obj.foo == foo 賦值給形參 fn
fn() // == foo() 屬於純粹調用,因此 this 的指向為 window
// 因此 this 的指向只需要關注函式最終是如何執行的即可
}
let obj = {
a:2,
foo
}
bar(obj.foo) // obj.foo == foo 作為實參
|
阿里巴巴面試題
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| var name = 222;
var a = {
name: 111,
say: function () {
console.log(this.name);
},
};
var fun = a.say;
fun(); // ?
a.say(); // ?
var b = {
name: 333,
say: function (fun) {
fun();
},
};
b.say(a.say); // ?
b.say = a.say;
b.say(); // ?
|
答案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| var name = 222;
var a = {
name: 111,
say: function () {
console.log(this.name);
},
};
var fun = a.say;
fun(); // 222
a.say(); // 111
var b = {
name: 333,
say: function (fun) {
fun();
},
};
b.say(a.say); // 222
b.say = a.say;
b.say(); // 333
|
forEach 內的 this 指向
1
2
3
4
5
6
7
| let arr = [1,2,3,4,5]
let obj = { name : "obj" }
// 在對應的 api 接口文檔中指明
arr.forEach(function(item, index, arr){
console.log(this); // 每次都輸出 obj
}, obj) // 這裡的 obj 就是 arr.forEach 遍歷時的 this 指向
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| arr.forEach(function callback(currentValue[, index[, array]]) {
// your iterator
}[, thisArg]);
// 參數:
// callback
// 這個 callback 函式將會把 Array 中的每一個元素作為參數,帶進本 callback 函式裡,每個元素各執行一次,接收三個參數:
// currentValue
// 代表目前被處理中的 Array 之中的那個元素。
// index (選擇性)
// 代表目前被處理中的 Array 之中的那個元素的 index。
// array (選擇性)
// 呼叫 forEach() 方法的那個 Array 本身,也就是上面語法中的 arr。
// thisArg (選擇性)
// 執行 callback 回呼函式的 this(即參考之 Object)值。
// source: https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
|
顯式綁定: call、apply、bind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function foo() {
console.log(this); // 打印
}
let obj = {
a: 2,
foo
}
let bar = obj.foo
obj.foo() // obj
// 以下三種方式都能改變 this 的指向,將 bar 函式 的 this 指向改為 obj
bar.call(obj) // obj
bar.apply(obj) // obj
bar.bind(obj)() // obj
|
1
2
3
4
5
6
7
| function foo(){
console.log(this)
}
foo.call(1) // Number {1}
foo.call(null) // this 指向 window
foo.apply(undefined) // this 指向 window
|
建構式的顯式綁定實例
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
| function Product(name, price) {
this.name = name;
this.price = price;
if (price < 0)
throw RangeError('Cannot create product "' + name + '" with a negative price');
return this;
}
function Food(name, price) {
// new 後 this 的指向就是這個實例物件
console.log(this);
Product.call(this, name, price);
this.category = 'food';
}
Food.prototype = new Product();
function Toy(name, price) {
Product.call(this, name, price);
this.category = 'toy';
}
Toy.prototype = new Product();
let cheese = new Food('feta', 5);
console.log(cheese); // Food {name: 'feta', price: 5, category: 'food'}
let fun = new Toy('robot', 40);
console.log(fun); // Toy {name: 'robot', price: 40, category: 'toy'}
|
new 綁定
1
2
3
4
5
6
7
8
| function Person(name='haewon', age=6){
this.name = name
this.age = age
console.log(this) // 這裡會打印 this
}
let me = new Person('pupu', 12) // 打印 Person {name: 'pupu', age: 12},this 的指向為實例化的物件
Person() // 打印 window; 同時 window.name 的值為 'haewon'; window.age 的值為 6
|
優先級
顯式綁定 > 隱式綁定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| function foo(){
console.log(this.a);
}
let obj1 ={
a:'obj1',
foo
}
let obj2 ={
a:'obj2',
foo
}
obj1.foo() // obj1
obj2.foo() // obj2
obj1.foo.call(obj2) // obj2
obj2.foo.call(obj1) // obj1
|
new 綁定 > 顯式綁定
1
2
3
4
5
6
7
8
9
10
11
12
13
| function foo(b){
this.a = b
}
let obj1 = {}
let bar = foo.bind(obj1)
bar(2)
console.log(obj1.a) // 2
let bar_child = new bar(99) // new 將 this 的指向更改為 bar_child
console.log(obj1.a); // 2
console.log(bar_child.a); // {a:99}
|
Arrow function 的 this 指向
物件中的巢狀函式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| let a = 0
let obj = {
a : 'obj',
foo() {
let that = this;
console.log(this);
function test() {
console.log(this); // window
console.log(that); // obj
}
test()
// 也可以通過這種方式使 this 指向 obj 👇🏻
test.call(this)
}
}
obj.foo()
|
箭頭函式中的 this
1
2
3
4
5
6
7
8
9
10
11
12
13
| let a = 0
let obj = {
a: 'obj',
foo() {
console.log(this); // obj
let test = ()=>{
console.log(this); // obj
}
test()
}
}
obj.foo() // => 箭頭函式內部沒有 this 指向,箭頭函式的 this 指向是由外層函式的作用域決定的
|
更改箭頭函式的 this 指向
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| function foo(){
let test = ()=>{
console.log(this);
}
return test
}
let obj1 = {
a: 'obj1',
foo
}
let obj2 = {
a: 'obj2',
foo
}
obj1.foo()() // 打印obj1
obj2.foo()() // 打印obj2
// => 默認綁定規則(純粹調用) 對箭頭函式無效,因為箭頭函式里面沒有 this
|
嘗試隱式綁定 this 指向
1
2
3
4
5
6
7
8
9
| let obj1 = {
a: 'obj1',
foo: ()=>{
console.log(this)
}
}
obj1.foo() // window
// => 隱式綁定 對箭頭函式無效
|
嘗試顯式綁定 this 指向
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| function foo(){
let test = ()=>{
console.log(this);
}
return test
}
let obj1 = {
a: 'obj1',
foo
}
foo()() // window
let bar = foo().call(obj1) // window
// => 顯式綁定 對箭頭函式無效
|
new 運算子能否改變箭頭函式的 this 指向?
1
2
3
4
5
6
7
| let foo = ()=>{
console.log(this);
}
let test = new foo()
// 報錯 Uncaught TypeError: foo is not a constructor
// => 箭頭函式不能作為建構式
// => 所有綁定規則對箭頭函式都不適用; 箭頭函式的 this 取決於父環境中的 this 指向; => 箭頭函式不存在 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
37
| var name = '我是 window 裡的 name'
let obj1 = {
name: 'obj1 的 name',
fn1(){
console.log(this.name);
},
fn2: ()=>{
console.log(this.name);
},
fn3(){
return function(){
console.log(this.name);
}
},
fn4(){
return () => {
console.log(this.name);
}
}
}
let obj2 = {
name : 'obj2 的 name'
}
obj1.fn1(); // ?
obj1.fn1.call(obj2); // ?
obj1.fn2(); // ?
obj1.fn2.call(obj2); // ?
obj1.fn3()(); // ?
obj1.fn3().call(obj2); // ?
obj1.fn3.call(obj2)(); // ?
obj1.fn4()() // ?
obj1.fn4().call(obj2); // ?
obj1.fn4.call(obj2)(); // ?
|
答案:
1
2
3
4
5
6
7
8
9
10
11
12
13
| obj1.fn1(); // 'obj1 的 name'
obj1.fn1.call(obj2); // 'obj2 的 name'
obj1.fn2(); // '我是 window 裡的 name'
obj1.fn2.call(obj2); // '我是 window 裡的 name'
obj1.fn3()(); // '我是 window 裡的 name'
obj1.fn3().call(obj2); // 'obj2 的 name'
obj1.fn3.call(obj2)(); // '我是 window 裡的 name'
obj1.fn4()() // 'obj1 的 name'
obj1.fn4().call(obj2); // obj1 的 name
obj1.fn4.call(obj2)(); // obj2 的 name
|
案例二
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
| function Foo(){
getName = function(){
console.log(1)
}
return this
}
Foo.getName = function(){
console.log(2);
}
Foo.prototype.getName = function(){
console.log(3);
}
let getName = function(){
console.log(4)
}
function getName(){
console.log(5)
}
// 請寫出輸出結果:
Foo.getName(); // ?
getName(); // ?
Foo().getName(); // ?
getName(); // ?
new Foo.getName(); // ?
new Foo().getName(); // ?
new new Foo().getName(); // ?
|
答案:
1
2
3
4
5
6
7
8
| Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3
|