參考網站 參考網站 參考網站 參考網站 參考網站
在 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 )());
Copy 你能答的出來嗎?如果不行的話,代表你沒有「完全」懂 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
Copy 在上面我們宣告了一個 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 ()
Copy 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
Copy 我們有一個叫做 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
Copy 就是如此簡單,你第一個參數傳什麼,裡面 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
Copy 原本 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
Copy 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
Copy 答案是不會改變 ,一但 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']
Copy 詳細可以參考我另一篇文章 - 「使用 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
Copy 這種跟一開始的物件導向範例不太一樣,這個範例是直接創造了一個物件而沒有透過 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
Copy 無論我在哪裡,無論我怎麼呼叫 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
Copy 明明就是同一個函式,怎麼第一次呼叫時 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
Copy 而規則就是你在呼叫 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 ()
Copy 你可以不要往下拉,先想一下那三個 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
Copy 特別講一下最後一個 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 )
Copy 只要按照我們之前說的,用 call 來轉換一下形式就好:
1
2
3
hello () // hello.call() => window(瀏覽器非嚴格模式)
a . hello () // a.hello.call(a) => a
b . hello . apply ( a ) => 直接用 apply , 所以就是 a
Copy 再來一題比較不一樣的,要看仔細囉(假設在瀏覽器底下跑,非嚴格模式):
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 ()
Copy 這題的話如果你搞錯,一定是你忘記了我們最重要的一句話:
要看 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
Copy 我們在 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>
}
}
Copy 最後 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>
}
}
Copy 還有另外一種方式是用箭頭函式:
1
2
3
4
5
6
7
class App extends React . Component {
render () {
return < button onClick = {() => {
console . log ( this )
}} > click < /button>
}
}
Copy 為什麼箭頭函式也可以?因為我們前面提過,「在宣告它的地方的 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
Copy 隱式綁定規則 (誰呼叫就指向誰) 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 ()
Copy 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 ()
Copy 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 ()
Copy 閉包 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
Copy 變數賦值的情況 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
Copy 參數賦值的情況 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 作為實參
Copy 阿里巴巴面試題 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 (); // ?
Copy 答案:
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
Copy 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 指向
Copy 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
Copy 顯式綁定: 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
Copy 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
Copy 建構式的顯式綁定實例 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'}
Copy 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
Copy 優先級 顯式綁定 > 隱式綁定 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
Copy 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}
Copy 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 ()
Copy 箭頭函式中的 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 指向是由外層函式的作用域決定的
Copy 更改箭頭函式的 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
Copy 嘗試隱式綁定 this 指向 1
2
3
4
5
6
7
8
9
let obj1 = {
a : 'obj1' ,
foo : ()=>{
console . log ( this )
}
}
obj1 . foo () // window
// => 隱式綁定 對箭頭函式無效
Copy 嘗試顯式綁定 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
// => 顯式綁定 對箭頭函式無效
Copy 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
Copy 案例一 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 )(); // ?
Copy 答案:
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
Copy 案例二 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 (); // ?
Copy 答案:
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
Copy