5分鐘徹底搞懂JavaScript中的this指向問題

語言: CN / TW / HK

theme: fancy

在Javascript中,this的指向靈活,使用場景多,面試中會被經常提及。由於this指向的靈活性,會在開發過程中產生一些不容易發現的BUG。

技術社群通常用一句話總結了this的指向問題:誰呼叫它,this就指向誰。也就是說,this的指向是在呼叫時確定它究竟指向誰的。

通過社群的技術文章,有人總結出幾條規律:

1.在函式體中非顯式或隱式地簡單呼叫函式時,在嚴格模式下,函式內的this會被繫結到undefined,在非嚴格模式下,則會繫結到全域性物件 window/global上。 2.一般使用new方法呼叫建構函式時,建構函式內的this指向會被繫結到新建立的物件上。 3.一般通過call/apply/bind方法顯式呼叫函式時,函式體內的this會被繫結到指定引數的物件上。 4.一般通過上下文物件呼叫函式時,函式體內的this會被繫結到該物件上。 5.在箭頭函式中,this的指向是由外層(函式或全域性)作用域來決定的。

我們各點進行分析下:

全域性環境中的this

非嚴格模式下this指向window

function f () { console.log(this) // window } 那麼上面的程式碼輸出則應該是window

在嚴格模式下函式體內的this指向undefined

```

funciton f () { 'use strict' console.log(this) //undefined } ```

通過use strict指明嚴格模式的情況下 this 執行則是undefined

下面看一道題目

``` const foo = { bar: 10, fu: function() { console.log(this) console.log(this.bar) } } var fn1 = foo.fn

fn1 ()

```

fn 函式在foo物件中用來作為物件的方法,但是在賦值給fn1之後,fn1仍然在全域性環境中執行,因此的列印結果就是 console.log(window) console.log(window.bar) 如果把,這道題改為如下形式

const foo = { bar: 10, fu: function() { console.log(this) console.log(this.bar) } } fo.fn() 則輸出的是 {bar: 10, fn: f} 10 這時,this指向的是最後呼叫它的物件,所以this指向的是foo物件,在執行函式時不考慮顯式繫結,如果函式中的this是被上一級的物件呼叫,那麼this指向的就是上一級的物件;否則指向全域性環境。

上下文物件呼叫中的this

通過上面的結論我們分析下下面的程式碼

``` const person = { name: 'jujin', brother: { name: 'kejinan', fn: function () { return this.name } } } console.log(person.brother.fn())

`` fn 是被它的上一級呼叫,所以this 應該指向brother,所以this.name ==== 'kejinan'`

通過bind,call,apply改變this指向

用一句話總結,他們都是用來改變相關函式的this指向的,但是call和apply是直接進行函式的呼叫,bind不會執行相關函式,而是返回一個新的函式,並且自動綁定了新的this,需要手動呼叫。 用程式碼來總結,下面的3段程式碼是等價的

``` // 1 const target = {} fn.call(target, 'arg1', 'arg2')

// 2 const target = {} fn.apply(target, ['arg1', 'arg2'])

// 3 const target = {} fn.bind(taget, 'arg1', 'arg2')() ```

下面我們來分析下這道題 const foo = { name: 'juejin', logName: function () { console.log(this.name) } } const bar = { name:"kejinan" } console.log(foo.logName.call(bar)) 通過call 將this指向繫結到了bar物件上,則輸出結果是kejinan

建構函式和this

function Foo() { this.bar = 'bar' } const instance = new Foo() console.log(instace.bar) 執行會輸出bar,需要注意的是,在建構函式中如果出現了return的情況時,可以分為兩種場景:

//場景1 function Foo() { this.user = 'juejin' const o = {} return o } const instance = new Foo() console.log(instance.user) 執行程式碼則會輸出undefined,此時instance返回的物件是控物件o.

//場景2 function Foo(){ this.user = 'juejin' return 1 } const instance = new Foo() console.log(instance.user) 執行程式碼將會輸入jueji,也就是說instance返回的目標物件是例項this。 所以,如果建構函式中顯式返回一個值,且返回的是一個物件,那麼this就執行的這個返回的物件,如果返回的不是一個物件,那麼this仍然指向例項。

箭頭函式中的this

在箭頭函式中,this指向的是由外層作用域來決定的

``` const foo = { fn: function () { setTimeout(function() { console.log(this) }) } } console.log(foo.fn())

`` 在這段程式碼中,this出現中setTimeout()的匿名函式中,因此this指向的是window`物件。

如果需要讓this指向foo物件,則可以用箭頭函式來處理

const foo = { fn: function () { setTimeout(() => { console.log(this) }) } } console.log(foo.fn())

總結

通過本篇內容的學習,我們看到this的使用場景繁多,確實不容易徹底掌握。根據文中提到的5點總結再結合程式碼理解起來容易很多,只有死記才能用活。

參考資料

  • 前端開發核心知識進階-從夯實基礎到突破瓶頸 - 侯策 - 中國工信出版集團 ISBN 978-7-121-38934-4
「其他文章」