天天看點

js中this到底指向誰

什麼是this

JavaScript中的this是什麼?

定義:this是包含它的函數作為方法被調用時所屬的對象。

function fn1(){
	this.name = "halo";
}
fn1();
           
  • 我們将定義拆分一下
    • 包含它的函數:包含this的函數是fn1。
    • 作為方法被調用:fn1(); 此處fn1函數被調用。
    • 所屬的對象:函數式調用函數預設所屬的對象是window。

通過上面三點分析,很容易知道fn1函數裡的this指向的是window。

那麼如果是更複雜的場景我們如何判斷this的指向呢?

this到底指向誰

如果想用一句話總結this的指向,稍微了解一些

this

指向的人都能脫口而出

誰調用它,this就指向誰。
           

也就是說

this

的指向是在調用時确定的,而不是在定義時确定的。這麼說沒有錯,但是并不全面。

其實,調用函數會建立新的術語函數自身的執行上下文。執行上下文的調用建立階段會決定

this

的指向。是以更加準确的總結應該是:

this的指向,是在調用函數時根據執行上下文所動态确定的。
           

在es6箭頭函數之前,想要判斷一個函數内部this指向誰,就根據以下四種方式來決定的。

  1. 函數式調用
  2. 上下文對象調用
  3. 構造函數調用
  4. bind、call、apply改變this指向

1、函數式調用

先來看一種相對簡單的情況,函數在全局環境中被直接調用,嚴格模式下函數内this指向undefined,非嚴格模式下函數内this指向window。如下

function fn1() {
	console.log(this)
}

function fn2() {
	'use strict'
	console.log(this)
}

fn1() // window
fn2() // undefined
           

再看下面例子:

const age = 18;
const p = {
     age:15,
     say:function(){
     	 console.log(this)
         console.log(this.age)
     }
}
var s1 = p.say
s1()       
           

這裡

say

方法内的

this

仍然指向

window

,因為

p

中的

say

函數指派給

s1

後,

s1

的執行仍然是在

window

的全局環境中。是以上面的代碼最後輸出

window

undefined

這裡可能有人會有疑問,如果是在全局環境中,那

this.age

不是應該輸出18麼?這是因為使用

const

聲明的變量不會挂載到

window

全局對象上,是以

this

指向

window

時找不到

window

上的

age

。換成var聲明即可輸出18.

如果想讓代碼輸出

p

中的

age

,并且讓say函數中的this指向對象

p

。隻需要改變函數的調用方式,如下

const age = 18;
const p = {
    age:15,
    say:function(){
    	console.log(this)
        console.log(this.age)
    }
}
p.say()
           

輸出

{age: 15, say: ƒ}
	15
           

因為此刻

say

函數内部的

this

指向的是最後調用它的對象。再次驗證了那句話,this的指向,是在調用函數時根據執行上下文所動态确定的。

2、上下文對象調用

const p = {
	age: 18,
	fn: function() {
		return this
	}
}

console.log(p.fn() === p)
           

輸出

true
           

如果第一節的函數式調用了解了。那麼這裡應該也不會有疑問。

我們再重複一遍this的指向,是在調用函數時根據執行上下文所動态确定的。

記住這句話後遇到更複雜的場景也可以很容易的确定this指向。

如下:

const p = {
    age: 20,
    child: {
        age: 18,
        fn: function() {
            return this.age
        }
    }
}
console.log(p.child.fn())
           

不論淺套關系如何變化,this都隻想最後調用它的對象,是以輸出18。

再更新下代碼:

const o1 = {
    text: 'o1',
    fn: function() {
        return this.text
    }
}
const o2 = {
    text: 'o2',
    fn: function() {
        return o1.fn()
    }
}
const o3 = {
    text: 'o3',
    fn: function() {
        var fn = o1.fn
        return fn()
    }
}

console.log(o1.fn())
console.log(o2.fn())
console.log(o3.fn())
           

輸出結果

o1
o1
undefined
           
  • 第一個,應該沒有問題,直接找到調用this的那個函數。
  • 第二個,看似調用

    o2.fn()

    ,其實内部調用的是

    o1.fn()

    ,是以還是輸出

    o1

  • 第三個,指派後調用

    fn()

    ,相當于在全局環境調用函數。

    this

    指向

    window

    如果現在的需求是想讓

輸出o2,代碼該如何修改?

如下:

const o1 = {
    text: 'o1',
    fn: function() {
        return this.text
    }
}
const o2 = {
    text: 'o2',
    fn: o1.fn
}

console.log(o2.fn())
           

3、構造函數調用

function Foo() {
    this.age = 18
}
const instance = new Foo()
console.log(instance.age)
           

輸出18。知道輸出結果并不難,但是

new

操作符調用構造函數時都做了什麼呢?

  • 建立一個新對象;
  • 将構造函數的

    this

    指向這個新對象;
  • 為新對象添加屬性、方法;
  • 傳回新對象。

需要注意的是,如果在構造函數中出現顯式的

return

,那麼就要分為兩種場景分析。

function Foo(){
    this.age = 18
    const o = {}
    return o
}
const instance = new Foo()
console.log(instance.age)
           

将會輸出

undefined

,因為如果在構造函數中出現顯式的

return

,并且傳回一個對象時,那麼建立的構造函數執行個體就是

return

傳回的對象,這裡

instance

就是傳回的空對象

o

function Foo(){
    this.age = 18
    return 1
}
const instance = new Foo()
console.log(instance.age)
           

将會輸出

18

,因為如果構造函數中出現顯式

return

,但是傳回一個非對象的值時,那麼

this

還是指向執行個體。

總結:當構造函數顯式傳回一個值,并且傳回的是一個對象,那麼

this

就指向這個傳回的對象。如果傳回的不是一個對象,

this

仍然指向執行個體。

4、bind、call、apply改變this指向

關于基礎用法,這裡不再贅述。需要知道的是

bind/call/apply

三者都是改變函數

this

指向的,

call/apply

是改變的同時直接進行函數調用,而

bind

隻是改變

this

指向,并且傳回一個新的函數,不會調用函數。

call

apply

的差別就是參數格式不同。詳見如下代碼:

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

上述代碼等同于如下代碼

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

可以看出知識調用的參數形式不同而已,改寫成bind如下所示

const target = {}
fn.bind(target, 'arg1', 'arg2')()
           

不光要調用bind傳入參數,還是在調用bind後再次執行函數。

明白call/apply/bind的使用後,再來看一段代碼:

const foo = {
    age: 18,
    showAge: function() {
        console.log(this.age)
    }
}
const target = {
    age: 22
}
console.log(foo.showAge.call(target))
           

結果輸出22,隻要掌握了

call/apply/bind

的基本用法,對于輸出結果并不難了解。我們往往會遇到多種方式同時出現的情況,我們在說完箭頭函數的

this

後會再詳細說明

this

優先級相關内容。

5、箭頭函數this指向

熟悉es6的人應該會知道箭頭函數中的

this

指向,不再遵從上述的規制,而是根據外層的上下文來決定。

es5代碼:

const foo = {  
    fn: function () {  
        setTimeout(function() {  
            console.log(this)
        })
    }  
}  
foo.fn()  // Window{……}
           

this

出現在

setTimeout()

中的匿名函數裡時,

this

指向

window

對象。這種特性勢必會給我們的開發帶來一些坑,es6的箭頭函數就很好的解決了這個問題。

es6代碼:

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

箭頭函數中的

this

指向,不再适用上面的标準,而是找到外層上下文,這段代碼中

this

在箭頭函數中,則找到外層的上下文的調用對象——

foo

。是以這裡的

this

指向的就是

foo

注意:當箭頭函數改變了

this

指向後,那麼該

this

指向就不再受任何影響,也就是說不會再次發生改變,具體在

this

優先級章節中會舉例說明。

總結:

  • 通過

    call、apply、bind、new

    等改成

    this

    指向的操作稱為顯式綁定;
  • 根據上下文關系确定的

    this

    指向成為隐式綁定。

如果一段代碼中即出現顯式綁定又有隐式綁定,該如何确定

this

指向呢?

往下看

6、this優先級

function foo (age) {
    console.log(this.age)
}

const o1 = {
    age: 1,
    foo: foo
}

const o2 = {
    age: 2,
    foo: foo
}

o1.foo.call(o2)
o2.foo.call(o1)
           

如果隐式綁定優先級高于顯式綁定,那麼應該輸出

1,2

。但是運作代碼發現結果輸出

2,1

。這也就說明了顯式綁定中的

call、apply

優先級更高。

再看:

function foo (age) {
    this.age = age
}

const o1 = {}

var fn = foo.bind(o1)
fn(18)
console.log(o1.age) // 18

var f1 = new fn(22)
console.log(f1.age); // 22
           

分析下上面代碼,

fn

foo

函數調用

bind

方法傳回的函數,也就相當于是傳回

foo

函數,并且将

this

指向

o1

對象。執行了

fn(18)

o1

對象的

age

值就是18了,是以第一個輸出結果是18。

然後通過

new

調用

fn

函數,這時

fn

函數作為構造函數被調用,

this

就會指向傳回的執行個體,進而與

o1

對象解綁。

是以得出結論:

new

的優先級高于

bind

還記得上一節提到的箭頭函數特行麼?箭頭函數影響的this指向無法被修改。看下面代碼:

function foo() {
    return () => {
        console.log(this.age)
    };
}

const o1 = {
    age: 2
}

const o2 = {
    age: 3
}

const fn = foo.call(o1)
console.log(fn.call(o2))
           

輸出為2,

foo

this

指向了

o1

fn

接收的箭頭函數的

this

自然也會指向

o1

。而箭頭函數的this是不會再次改變的,是以盡管用顯式綁定

call

去改變

this

指向,也是不起作用的。

結束啦!

this涉及知識點繁多,碰到優先級問題也是讓人頭疼。

沒有什麼捷徑,唯有“死記硬背”+“慢慢了解”

繼續閱讀