JavaScript中的this是什麼?
定義:this是包含它的函數做爲方法==被調用時所屬的對象。==es6
function fn1(){ this.name = "halo"; } fn1();
咱們將定義拆分一下app
經過上面三點分析,很容易知道fn1函數裏的this指向的是window。
那麼若是是更復雜的場景咱們如何判斷this的指向呢?函數
若是想用一句話總結this的指向,稍微瞭解一些this
指向的人都能脫口而出this
誰調用它,this就指向誰。
也就是說this
的指向是在調用時肯定的,而不是在定義時肯定的。這麼說沒有錯,可是並不全面。
其實,調用函數會建立新的術語函數自身的==執行上下文==。執行上下文的調用建立階段會決定this
的指向。因此更加準確的總結應該是:es5
this的指向,是在調用函數時根據執行上下文所動態肯定的。
在es6箭頭函數以前,想要判斷一個函數內部this指向誰,就根據如下四種方式來決定的。code
先來看一種相對簡單的狀況,函數在全局環境中被直接調用,嚴格模式下函數內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
的全局環境中。所以上面的代碼最後輸出window
和undefined
。開發
這裏可能有人會有疑問,若是是在全局環境中,那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
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
指向的,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
優先級相關內容。
熟悉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
指向呢?
往下看
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涉及知識點繁多,碰到優先級問題也是讓人頭疼。沒有什麼捷徑,惟有「死記硬背」+「慢慢理解」