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
「其他文章」