this
做爲JS
語言中的關鍵字,其複雜的指向每每是初學者混淆的地方,同時也是面試官常常詢問的考點之一,理解好JS
中的this
指向,才能算是邁入了JS
這門語言的門檻,本文將梳理出this
指向的不一樣情景,以及如何更好、更準確的判斷出this指向,讓面試的時候再也不爲this
指向頭疼。git
在ES5中,判斷this
指向的關鍵是在於這句話,this永遠指向調用它的那個對象,想要理解本句話就只有一個重要的點,是誰調用了this
,只要從這個點出發,找到這個誰,那麼就能輕車熟路的判斷this
的指向。es6
This 被分爲三種狀況:全局對象、當前對象或者任意對象;判斷處於哪一種狀況,徹底取決於函數的調用方式github
從這句話能夠看出,this
調用的指向,其實取決於函數的調用方式,也就是在運行時,哪一個函數內部使用了this
,那麼this
就指向調用此函數的對象。這樣就很容易理解this
指向了,看到判斷this
指向問題的第一反應,去找使用了this
這個函數被誰調用就完事了。如,A函數中使用了this
,那麼就去找哪一個對象調用了A函數。面試
麻煩就麻煩在,如何去找到哪一個對象調用了使用this
的函數,由於函數的調用在JS
中是能夠經過多種方式的。數組
apply
或 call
調用那麼問題就變成了只要判斷函數被哪一個對象調用就好了。由此分析下四種不一樣的函數調用狀況(針對ES5)瀏覽器
var a = 'arzh';
function thisName() {
console.log(this.a);
}
thisName(); //'arzh'
console.log(window);
複製代碼
咱們拿這個thisName
函數分析一下,做爲普通函數調用時,掛載在全局,可輕易判斷出函數被window
調用了,那麼就能夠判斷出this
指向了window
,this.a
就至關於window.a
,由於在ES5中定義的全局變量會變成window上的屬性 bash
window.a === this.a === arzh
;
若是把函數改爲這樣app
var a = 'arzh';
function thisName() {
var a = 'arzh1'
console.log(this.a);
}
thisName(); //'arzh'
複製代碼
這個也好理解,由於this
始終都是指向window
,那麼window
中a
變量一直都是'arzh',由於thisName
函數中的a
變量不會覆蓋了window
上的a
變量。函數
咱們再把函數變一下oop
var a = 'arzh';
function thisName() {
a = 'arzh1'
console.log(this.a);
}
thisName(); //'arzh1'
複製代碼
這個如何理解呢,同理這個時候是window
調用了thisName
函數,this
仍是始終指向window
,可是thisName
函數中a
變量沒有加var
,就被默認爲全局的變量,覆蓋了上一次的a
變量,因此this.a
就變成了最後一次定義a
變量的字符串,也就是'arzh1';
做爲對象方法調用,顧名思義,就是對象調用了這個函數(方法),那麼根據上面的分析,誰調用了使用this的函數,this就指向誰,可知this
確定就指向了此對象。用代碼看一下
var a = 'arzh';
var b = {
a : 'arzh1',
fn : function() {
console.log(this.a)
}
}
b.fn(); //'arzh1'
複製代碼
這個就很容易理解,fn
做爲b對象的方法調用,即b
對象調用了fn
函數,那麼this
指向了b
對象,因此this.a === b.a === 'arzh1'
修改一下函數,以下:
var a = 'arzh';
var b = {
a : 'arzh1',
fn : function() {
console.log(this.a)
}
}
window.b.fn(); //'arzh1'
複製代碼
window
調用了b
對象,b對象調用了fn方法,本質上,調用fn
方法的仍是b
對象,因此this
一直指向b
對象,也即輸出了'arzh1',若是將b
對象中的a
屬性去掉,那麼理論上就應該輸出undefined
,由於this
仍是在b
對象上,b
中並無a
屬性。代碼以下:
var a = 'arzh';
var b = {
//a : 'arzh1',
fn : function() {
console.log(this.a)
}
}
window.b.fn(); //undefined
複製代碼
咱們繼續往下看
var a = 'arzh';
var b = {
a : 'arzh1',
fn : function() {
console.log(this.a)
}
}
var fn1 = b.fn; //fn並無被調用
fn1(); //'arzh'
複製代碼
這種狀況也是同樣的,b
對象將fn
方法賦值給fn1
變量,那麼此時fn
方法並無被任何人調用,只是單純的賦值。執行fn1
方法以後,fn
方法纔算真正被調用,那麼此時fn1
掛載在全局window
上,也就是window
對象去調用了fn1
方法,因此fn1
的指向就變成了window
,也就輸出了'arzh';
那麼若是是這種狀況呢
var a = 'arzh';
var b = {
a : 'arzh1',
fn : function(){
setTimeout(function() {
console.log(this.a)
},100)
}
}
b.fn(); //arzh
複製代碼
從上述中能夠看到b.fn()
裏面執行了setTimeout
函數,setTimeout
內的this
是指向了window
對象,這是因爲setTimeout()
調用的代碼運行在與所在函數徹底分離的執行環境上。熟知EventLoop
的人員應該明白,setTimeout
函數其實調用的是瀏覽器提供的其餘線程,當JS
主線程走完以後,會調用任務隊列的函數,也便是setTimeout
函數,此時setTimeout
是在window
上調用的,那麼this
天然指向了window
。
apply
、call
函數均可以用來更改this
指向,具體的用法能夠參考apply和call.由於apply
與call
函數在使用上,基本只有傳入參數不一樣的區別(apply
第二個參數是數組,call
第二個參數不爲數組),本文拿call
來說解。咱們直接看代碼
var a = 'arzh';
var b = {
a : 'arzh1',
fn : function() {
console.log(this.a)
}
}
var c = {
a : 'arzh2'
}
b.fn(); //arzh1
b.fn.call(window); //arzh
b.fn.call(c); //arzh2
複製代碼
從代碼能夠看出,call
能夠直接把this
指向爲傳入的第一個參數。咱們這裏只是經過結果來得出call
能夠改變this指向,那麼問題來了,call
函數是怎麼改變this
指向的呢,咱們能夠看一下call
的簡單實現
Function.prototype.call = function(context) {
// 這裏的this是指調用call的函數
// b.fn.call => this就是fn,上述已經有相似的例子
context.fn = this;
context.fn();
delete context.fn;
}
複製代碼
看到這裏,應該就能明白call
是如何改變this
指向了吧,也是借用了做爲對象方法調用的原理,咱們只要把傳入call
的第一個參數當成一個對象, 而後直接調用方法,那麼this
天然就指向了這個對象了。咱們拿上述b.fn.call(window)
來講明一下
context
就是傳入的window
window.fn = this = window.fn
context.fn => window.fn() => window.fn()
JavaScript 中的構造函數也很特殊,構造函數,其實就是經過這個函數生成一個新對象(object),這時候的 this 就會指向這個新對象;若是不使用 new 調用,則和普通函數同樣
咱們知道在JS
中普通函數能夠直接當構造函數使用,使用new
來生成新的對象
function Name(b) {
this.b = b;
console.log(this)
}
var o = new Name('arzh');
//Name {b: "arzh"}
function Name(b) {
this.b = b;
console.log(this)
}
Name('arzh')
//Window
複製代碼
能夠看出若是是經過new
生成的對象,this
是指向已經生成的對象的。
咱們來分析下具體緣由:這裏咱們引用一下冴羽大大的new
實現,具體的實現過程能夠看這裏
function objectFactory() {
//新建立一個對象
var obj = new Object(),
//獲取函數參數的第一個
Constructor = [].shift.call(arguments);
//原型關聯
obj.__proto__ = Constructor.prototype;
//執行第一個參數的函數
Constructor.apply(obj, arguments);
//返回建立成功的對象
return obj;
};
複製代碼
這裏主要關注一下第四步,new
內部構造對象的時候,使用了apply
函數,apply
函數的一個參數就是咱們須要生成的對象obj
,根據上述的使用 apply 或 call 調用,咱們也就不難理解使用new
爲何this
會指向新生成對象了。
在es5
中,this
的指向能夠簡單的歸結爲一句話:this指向函數的調用者。那麼在ES6
中其實這句話在箭頭函數下是不成立的。咱們來看看這個例子
var a = 'arzh';
var b = {
a : 'arzh1',
fn : function() {
console.log(this.a)
}
}
b.fn(); //'arzh1'
複製代碼
var a = 'arzh';
var b = {
a : 'arzh1',
fn : () => {
console.log(this.a)
}
}
b.fn(); //'arzh'
複製代碼
經過這兩個例子,咱們能夠很明顯的發現,在箭頭函數中的this指向,跟在普通函數中的this
指向是不一樣的。
函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象
因此能夠這麼理解,在ES6
中this
的指向是固定的,this
綁定定義時所在的做用域,而不是指向運行時所在的做用域
上述例子中,箭頭函數輸出 'arzh' 是由於對象不構成單獨的做用域,致使fn
箭頭函數定義時的做用域就是全局做用域
this指向的固定化,並非由於箭頭函數內部有綁定this的機制,實際緣由是箭頭函數根本沒有本身的this,致使內部的this就是外層代碼塊的this。正是由於它沒有this,因此也就不能用做構造函數,因此固然也就不能用call()、apply()、bind()這些方法去改變this的指向
其實this
指向不難,比較麻煩的場景,只要用心去肯定哪一個對象調用了函數,就能很快肯定this
的指向,但願看了這篇文章,對你之後判斷this
指向有必定的用處
[1] 阮一峯博客es6教程
[2] 冴羽博客new模擬實現