js中this到底指向誰

什麼是this

JavaScript中的this是什麼?
定義:this是包含它的函數做爲方法==被調用時所屬的對象。==es6

function fn1(){
    this.name = "halo";
}
fn1();
  • 咱們將定義拆分一下app

    • 包含它的函數:包含this的函數是fn1。
    • 做爲方法被調用:fn1(); 此處fn1函數被調用。
    • 所屬的對象:函數式調用函數默認所屬的對象是window。

經過上面三點分析,很容易知道fn1函數裏的this指向的是window。
那麼若是是更復雜的場景咱們如何判斷this的指向呢?函數


this到底指向誰

若是想用一句話總結this的指向,稍微瞭解一些this指向的人都能脫口而出this

誰調用它,this就指向誰。

也就是說this的指向是在調用時肯定的,而不是在定義時肯定的。這麼說沒有錯,可是並不全面。
其實,調用函數會建立新的術語函數自身的==執行上下文==。執行上下文的調用建立階段會決定this的指向。因此更加準確的總結應該是:es5

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

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

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

一、函數式調用

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

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

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

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

再看下面例子:ip

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的全局環境中。所以上面的代碼最後輸出windowundefined開發

這裏可能有人會有疑問,若是是在全局環境中,那this.age不是應該輸出18麼?這是由於使用const聲明的變量不會掛載到window全局對象上,所以this指向window時找不到window上的age。換成var聲明便可輸出18.get

若是想讓代碼輸出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的指向,是在調用函數時根據執行上下文所動態肯定的。==

二、上下文對象調用

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

若是如今的需求是想讓

console.log(o2.fn())

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

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

console.log(o2.fn())

三、構造函數調用

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仍然指向實例。

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

關於基礎用法,這裏再也不贅述。須要知道的是bind/call/apply三者都是改變函數this指向的,call/apply是改變的同時直接進行函數調用,而bind只是改變this指向,而且返回一個新的函數,不會調用函數。callapply的區別就是參數格式不一樣。詳見以下代碼:

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優先級相關內容。

五、箭頭函數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指向呢?
往下看

六、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

分析下上面代碼,fnfoo函數調用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,foothis指向了o1fn接收的箭頭函數的this天然也會指向o1。==而箭頭函數的this是不會再次改變的==,因此儘管用顯式綁定call去改變this指向,也是不起做用的。


結束啦!this涉及知識點繁多,碰到優先級問題也是讓人頭疼。沒有什麼捷徑,惟有「死記硬背」+「慢慢理解」

相關文章
相關標籤/搜索