詳解javascript中的this的指向問題
首先,要明白this 既不指向函式自身,也不指函式的詞法作用域。this一般存在於函式中,表示當前函式的執行上下文,如果函式沒有執行,那麼this沒有內容,只有函式在執行後this才有繫結。
然後,我們來看看this四種繫結規則,也可以說在四種不同函式執行方式時this的指向。
1.預設繫結(執行)
預設執行:即沒有其他繫結規則存在時的預設規則。這也是函式呼叫中最常用的規則。this指向window,嚴格模式下指向undefined
function fn(){
console.log(this)
}
fn()
fn()
列印到控制檯結果為window
開啟嚴格模式後
function fn(){
"use strict"
console.log(this)
}
fn()
fn()
列印到控制檯結果為undefined
2.隱式繫結(執行)
通過物件執行(通過上下文物件執行) obj.fn():當前的執行物件
function fn(){ console.log(this.a) } var a = 10; var obj = { a:20, b:fn } var obj2 = { a:30, b:obj.b } obj2.b(); //??
obj2.b();
列印到控制檯結果為30
因為obj2.b();
在上述程式碼中等價於obj2.fn()
,所以此時this指向obj2
,所以列印結果為30;
多層呼叫
function fn(){ console.log(this.a) } var a = 10; var obj = { a:20, b:fn } var obj2 = { a:30, b:obj.b } var obj3 = { a:40, b:obj2 } obj3.b.b() //?
obj3.b.b()
列印結果為30
上述程式碼呼叫結果為obj3.b->obj2,obj2.b->obj.b->fn
,因為this只獲取最近一層呼叫的上下文物件,即obj2
,
所以結果為30
隱式丟失(函式別名)
注意:這裡存在一個陷阱,大家在分析呼叫過程時,要特別小心
function fn() {
console.log( this.a );
}
var a = 2;
var obj = {
a: 3,
b: fn
};
var bar = obj.b;
bar(); //?
bar()
列印結果為2
為什麼會這樣,obj.b
賦值給bar,那呼叫bar()為什麼沒有觸發隱式繫結,使用的是預設繫結呢。
這裡有個概念要理解清楚,obj.b
是引用屬性,賦值給bar的實際上就是fn
函式(即:bar指向fn
本身)。
那麼,實際的呼叫關係是:通過bar找到fn
函式,進行呼叫。整個呼叫過程並沒有obj的引數,所以是預設繫結,全域性屬性a。
隱士丟失(回撥函式)
function fn(){
console.log(this.a)
}
var a=20;
var obj = {
a:20,
b:fn
}
setTimeout(obj.b, 2000);
function setTimeout(cb,t){
// t
cb() //?
}
cb()
列印結果為20
同樣的道理,雖然參傳是obj.fn
,因為是引用關係,所以傳參實際上傳的就是fn
物件本身的引用。對於setTimeout
的呼叫,還是 setTimeout
-> 獲取引數中fn
的引用引數 -> 執行 fn
函式,中間沒有obj的參與。這裡依舊進行的是預設繫結。
3.顯示繫結(執行)
function fn(){
console.log(this.a)
}
var obj1= {
a:10
}
var obj2 ={
a:20
}
var obj3 ={
a:30
}
fn.bind(obj1)();
fn.call(obj2);
fn.apply(obj3);
fn.bind(obj1)();
列印結果為10
fn.call(obj2);
列印結果為20
fn.apply(obj3);
列印結果為30
這裡要注意fn.bind(obj1)
後面還要加一個括號才執行!
4.new 繫結(建構函式執行(通過new執行))
js中的new操作符,和其他語言中(如JAVA)的new機制是不一樣的。js中,它就是一個普通函式呼叫,只是被new修飾了而已。
使用new來呼叫函式,會自動執行如下操作:
如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件。
從這點可以看出,this指向的就是物件本身。
function foo(a) {
this.a = a;
}
var a = 2;
var bar1 = new foo(3);
console.log(bar1.a); // ?
var bar2 = new foo(4);
console.log(bar2.a); // ?
因為每次呼叫生成的是全新的物件,該物件又會自動繫結到this上,所以列印結果分別為3,4
繫結規則優先順序
優先順序是這樣的,以按照下面的順序來進行判斷:
數是否在new中呼叫(new繫結)?如果是的話this繫結的是新建立的物件。
數是否通過call、apply(顯式繫結)或者硬繫結呼叫?如果是的話,this繫結的是 指定的物件。
數是否在某個上下文物件中呼叫(隱式繫結)?如果是的話,this繫結的是那個上下文物件。
果都不是的話,使用預設繫結。如果在嚴格模式下,就繫結到undefined,否則繫結到 全域性物件