javascript中的this關鍵字
上一張初略版this分析導圖先:
this在英文中是一個指示代詞,指示將要表達中的名詞。
js王國中的this有會是指向誰了,先看看this常有的誤解認識
-
誤解1: this指向函數本身
function fun(){ this.count++ console.log('invoke fun') } fun.count=0 fun() console.log(fun.count) 輸出: invoke fun 0
上面的代碼并沒有輸出1, 可見this并非指向函數本身
-
誤解2: this指向當前作用域
function f1(){ var a = 'inner.f1' this.f2() } function f2(){ console.log('a='+this.a) } var a = 'global' f1() 輸出: a=global
同樣上面的代碼然而并沒有輸出'a=inner.f1',這樣this也并非指向當前作用域。
上面已經描述this不是什么了,那么this到底該指向什么了。 其實在js中一個函數被調用時,引擎會創建一個執行上下文記錄,該記錄包含函數調用位置,函數調用方式,傳入的參數信息。this就是執行上下文記錄中的一個屬性。 this是動態綁定的,并非編寫代碼時決定的。通過分析函數的調用位置,分析出this的綁定對象。這其中有四大綁定規則。
一、綁定規則:
-
默認綁定
函數作為獨立調用方式時,其采用默認綁定,或者無法應用其他綁定規則時的使用的默認綁定規則。默認綁定時,this指向全局對象。
例如:function fun(){ console.log('name:'+this.name) } var name = 'global' fun() 輸出: name:global
fun函數在上面代碼中使用獨立調用方式,this指向了全局對象,name作為全局定義的對象。
*** 當上述代碼fun內部運行在strict mode下時,全局定義的對象不能用于默認綁定,同時會報錯TypeError: this is undefined。 -
隱式綁定
一個對象中含有指向函數的引用屬性,并通過這個屬性間接引用函數,從而把this隱式綁定到這個對象。這就是隱式綁定。簡單點說:
當函數有被對象包含著時,這是函數內的this便指向被包含對的對象,上代碼示例:function fun(){ console.log('fun name:'+this.name) } var obj = { name: 'obj', fun: fun, funX: function(){ console.log('funX name:'+this.name) } } obj.fun() obj.funX() 輸出: fun name:obj funX name:obj
可以看到上面代碼中調用方式:obj.fun/obj.funX, 這是函數內部的this指向了obj對象,這樣this.name就會使用了obj.name
-
顯示綁定
如果按照隱式綁定中的方法調用,但不想要this綁定到默認對象,可以通過js中另外兩種調用方式apply/call,這兩種方法中第一個參數都是為顯示為函數內部this指向的對象,上代碼示例:var objA={ name: 'objA' } var objB={ name: 'objB', fun: function(){ console.log('name:' + this.name) } } objB.fun() objB.fun.call(objA) objB.fun.apply(objA) 輸出: name:objB name:objA name:objA
上面代碼中,objB.fun()使用了隱式綁定,輸出了objB中的name屬性值,另外兩種調用方式則改變了函數中的this對象使其綁定到了對象objA.
***同時還有一種顯示綁定方式bind,將其稱為硬綁定。
var objA={ name: 'objA' } var objB={ name: 'objB', fun: function(){ console.log('name:' + this.name) } } var fun = objB.fun.bind(objA) fun() 輸出: name:objA
上面代碼中通過bind方式定義了一個函數引用fun,后續調用時,其中的this指向了定義時的對象,故而輸出了objA,name值
-
new綁定
在函數作為構造函數方式調用時,函數內的this指向了新創建的對象。
例如:function Fun(name){ this.name = name } var fun = new Fun('fun') console.log(fun.name) 輸出: fun
上述函數Fun作為構造函數方式調用(其實Fun也是js中一名普通的函數),其實此時發生了如下幾件事:
- 創建一個對象
- 將新創建的對象綁定到函數中的this上
- 新創建的對象的[[prototype]]指向了Fun.prototype
- 如果函數內部沒有顯示返回值,那么new表達式執行完后默認返回該新創建的對象
所以如上顯示的那樣,輸出了'fun'
有了上面描述的四大綁定規則,那么在實際的函數方法中,該使用哪種綁定規則判斷this了,此時還需要了解這其中的優先級關系。
二、優先級判斷
唔有疑問,默認綁定無疑是四種優先級最低的,先擱置一邊,探索下其余三種的優先級。
隱式綁定 VS 顯示綁定
var objA={
name: 'objA'
}
var objB={
name: 'objB',
fun: function(){
console.log('name:' + this.name)
}
}
objB.fun()
objB.fun.call(objA)
name:objB
name:objA
上述代碼可以看出顯示綁定優先級高于隱式綁定規則: 顯示綁定 > 隱式綁定
接下來在看下隱式綁定與new綁定的優先級關系:
隱式綁定 VS new綁定
var obj={
name: null,
setName: function(name){
this.name = name
}
}
obj.setName('obj')
console.log('obj.name:' + obj.name)
var obj2 = new obj.setName('obj2')
console.log('obj.name:' + obj.name)
console.log('obj2.name:' + obj2.name)
輸出:
obj.name:obj
obj.name:obj
obj2.name:obj2
上述代碼中new obj.setName('obj2'), 并非更改了obj對象中的name屬性,可見new綁定的優先級高于隱式綁定: new綁定 > 隱式綁定
最后看下顯示綁定與new綁定方式優先級別:
顯示綁定 VS new綁定方式
apply/call方式不能與new連用,此處使用硬綁定方式bind與new連用
var obj={
name:null
}
function setName(name){
this.name = name
}
var newSetName = setName.bind(obj)
var newObj = new newSetName('new')
console.log('obj.name:'+obj.name)
console.log('newObj.name:'+newObj.name)
輸出:
obj.name:null
newObj.name:new
上面代碼看出及時setName顯示硬綁定this為obj,但是new方式調用時還是更改了setName中的this引用指向,由此可見new綁定優先級高于顯示綁定:new綁定 > 顯示綁定
那么總結上面的優先級規則:
new綁定 > 顯示綁定 > 隱式綁定 > 默認綁定
平常情況按照如上規則判斷均會有效,但凡事均會有例外,再看下例外的規則
例外1. 顯示綁定apply/call/bind時傳入了null/undefined時,此時this并非指向了null/undefined,而是使用了默認綁定規則
function fun(){
console.log(this.name)
}
var name = 'outer'
fun.call(null)
輸出
outer
上述this.name輸出了全局變量name值。
當然你會問為什么需要傳入null值,有一種case如需要使用bind預設值一些參數值,如:
function multiple(mul, number){
console.log('result:' + (mul * number))
}
var fun = multiple.bind(null, 5)
fun(2)
輸出:
result:10
例外2. 函數賦值方式形成的間接調用,此時this也是使用了默認綁定規則
function print(){
console.log(this.name)
}
var name = 'outer'
var obj={
name: 'inner',
print: print
}
var fun
(fun = obj.print)()
輸出:
outer
寫在最后
ES6中的箭頭函數,this的規則比較特殊,它是繼承了外層的this對象,由外層的作用域來決定。
上代碼:
function fun(){
return () => {
console.log(this.name)
}
}
var obj1={
name:'obj1'
}
var obj2={
name:'obj2'
}
var reFun = fun.call(obj1)
reFun.call(obj2)
輸出:
obj1
上述代碼可以看出并沒有輸出obj2,而是輸出了fun函數調用時this指向的對象obj1對應的name屬性,可以看出,箭頭函數中的this指向了外層函數中的this對象。可以看出箭頭函數有些反this機制,而是采用了“靜態”綁定。
參考資料:[美]KYLE SIMPSON著 趙望野 梁杰 譯 《你不知道的javascript》上卷