javascript 函式作用域中的關鍵字this的指向

深谷逸風發表於2019-03-04

對於初學者來說,關鍵字this總是讓人捉摸不透,甚是複雜,但是其實,只要你摸透了其中關鍵所在,便也無甚煩惱了;

其實this在函式中指向的是什麼,都是圍繞兩個原則,就是誰引用他他就指向誰與就近原則;

一般this的指向性問題分為下面幾類,我們來看具體例項

一、普通函式中的this關鍵字

//示例一
function f1(){
	this.a = 5;
}
f1();
console.log(window.a) // 5;

//示例二
var b = 10;
function f2(){
	this.b = 20;
}
f2();
console.log(window.b) //20;
複製程式碼

上面兩個例子都是普通函式,他們指向的都是window,所以我們的結論是:
普通函式中this的指向都是window

可能你會問,上面不是說,this指向的都是呼叫他的那個物件嗎?那函式自執行,this的指向不應該是函式本身嗎?

其實啊,函式自己呼叫自己的時候,就相當於是window在呼叫他,像示例一種,f1()相當於是window.f1();

既然是window呼叫他,那麼this的指向就是window了

二、物件中的this關鍵字

同樣我們先看下面例子:

//示例一
var obj1 = {
	a : 5,
	f1 : function(){
		console.log(this.a) // 5
		console.log(this === obj1) //true
	}
}
obj1.f1(); 

複製程式碼

從給出的例子我們可以看出來,當函式作為物件的屬性來呼叫時,this的指向便指向了物件自身,這就是說既然你函式都是我物件的一個屬性了,那麼你裡面的this當然指向我,這種指向是一種隱式繫結this的現象,但是如果我們解除了這種隱式繫結呢?

接下來我們將例子變一下:

//變異示例一
var obj2 = {
	a : 5,
	f2 : function(){
		console.log(this.a); //undefined
		console.log(this === obj2) //false
	}
}
var init = obj2.f2 //重點看這裡
init();
複製程式碼

還是那句話,誰呼叫指向誰,從變異示例一可以看出,obj.f2這裡並沒有呼叫函式f2,他只是將函式賦值給了變數init而已,所以這裡呼叫函式f2的不再是物件obj2了,因為已經將f2的隱式繫結給解除了

所以必然this指向的不在是obj2,那麼obj2中的屬性自然是呼叫不到了,this也不可能等於obj2了,那麼這裡的this究竟指向了誰呢?

一起看init()這裡,在這裡呼叫了函式,變數的是全域性變數,他的頂級物件在這裡是window,所以init() === window.init()是一樣的,所以最後this指向的是window;

這裡我們要注意,一定要看清楚函式是在哪裡呼叫的,這樣我們才能去判斷this指向的是什麼

再將上面的示例變一下:

var obj3 = {
	a : 5,
	obj4 : {
		a : 10,
		f3 : function(){
			console.log(this.a) //10
		}
	}
}
obj3.obj4.f3();
複製程式碼

這裡輸出的為什麼是10呢?按最終呼叫的應該是物件obj3,那麼他下面的a不應該是5嗎?

還記得我們上面說過一個詞叫就近原則嗎?雖然最終呼叫的是obj3,但是在遵循就近原則的情況下,呼叫他的是obj4,所以this指向的是obj4,那麼必然列印出來的就是10了

最後我們附加一個示例,這也是之前有個人有些疑問的地方,我覺得值得說一下

var obj5 = {
	a : 5,
	f5 : function(){
		console.log(this.a) // 5
		console.log(this === obj5) //true
	}
}

var init = obj5;
init.f5();
複製程式碼

在這裡直接將物件obj5賦值給變數init,然後由變數去呼叫f5,實則和obj5去呼叫f5是一樣的,this指向的還是物件obj5,所以在這裡一定要注意,千萬不要認為是window呼叫函式f5

三、call中的this關鍵字

call() 、bind()、apply()這類方法繫結this關鍵字又稱為顯示繫結,明面上我們完全可以看到他指向了誰

function f7(){
	this.x = function(){
		console.log(this.a) // 20
	}
}
var obj5 = {
	a : 20
}
f7.call(obj5);
obj5.x();
複製程式碼

通過call()方法,函式f7被封裝為物件obj5的一個屬性,this的值指向了呼叫函式的物件obj5了,

通過對call()方法的使用,我們知道,如果第一個引數為null,或者不寫,那麼其間的物件就是window,我們看下面示例

function f8(){
	this.x = function(){
		console.log(this.a) // 15
	}
}
var obj6 = {
	a : 20
}
var a = 15;
f8.call(null);
x();
//我們可以看到,這段函式裡面並沒有定義過函式x,但是在這裡可以直接呼叫
//這說明,我們的this指向了window,x() === window.x();
複製程式碼

四、建構函式中的this關鍵字

先看示例:

function F1(){
	this.a = 10;
	this.b = 20;
	this.f6 = function(){
		this.c = this.a+this.b;
		console.log(this.c); //30
		console.log(this === F1) //false
	}
}

var oInit = new F1();
oInit.f6();
複製程式碼

這裡就很奇怪了,明明所有症狀都表現為this指向的就是建構函式F1,但是為什麼this沒有指向F1呢,在這裡,我們需要了解,在new F1()的這個過程中,new到底做了什麼?

看下面程式碼:

//new 過程如下
{}
//new 先建立了一個空物件{}(第一步)
{}.__proto__ = F1.prototype 
//空物件的隱式原型指向F1的原型物件(第二步)
F1.call({});
//利用call方法繼承了F1中所有屬性與方法,這一步將F1內部的this指向指向了這個空物件
// 繼而會執行這個空物件繼承而來的內容,在最後
return this; 
//在我們不主動改變return 內容的時候,預設會將this返回出去	(第四步)
//而Oinit = {}
// 自然而然的最後this就指向了Oinit
複製程式碼

我看到很多人說,建構函式中的this指向的是建構函式本身,這種說法是不正確的,
他就算瞎指也不會指向函式本身,誰去呼叫他是吧,他指向的new的例項,所以一定要注意這裡

在建構函式中使用return有個特殊情況,我們來看看

//示例一
function f9(){
	this.a = 10;
	var obj10 = {};
	obj10.a = 20;
	return this.a
}
var init2 = new f9();
init2.a;


//示例二
function f9(){
	this.a = 10;
	var obj10 = {};
	obj10.a = 20;
	return obj10;
}
var init2 = new f9();
init2.a;

複製程式碼

這兩個示例有點特殊,特殊在於return出來的值,這裡先不說this指向的問題,說說return返回的值的問題,在函式中如果返回的是一個物件(包括函式,陣列,物件),即返回出來的是物件本身,就會覆蓋原來的this物件,如果返回的是值型別, 則是返回this物件;

所以從表面上看是覺得建構函式的例項化物件在呼叫引數,其實不然,內部正真呼叫的是返回的物件,那麼自然this值指向了返回的物件

五、事件中的this關鍵字

1、事件中直接使用this

<div class="clickMe">click me</div>
<script>
	var clickDom = document.querySelector(`.clickMe`);

	clickDom.onclick = function(){
		console.log(this.className) //clickMe
	}
</script>
複製程式碼

這裡this指向了操作事件的元素,所以如果直接在事件中使用this,那麼this則指向操作事件的元素;

2、事件中的函式使用this;

<div class="clickMe">click me</div>
<script>
	var clickDom = document.querySelector(`.clickMe`);

	clickDom.onclick = function(){
		function test(){
			console.log(this.className) //undefined
			console.log(this) //[object Window]
		}
		test();
	}
</script>
複製程式碼

這裡指向了window,我們是不是可以得出結論,如果在事件中的函式去呼叫this,那麼就看該函式是誰呼叫的呢?先不急,我們再看下面一個示例:

<div class="clickMe">click me</div>
<script>
	var clickDom = document.querySelector(`.clickMe`);

	clickDom.onclick = function(){
		var obj7 = {
			a : 10,
			f7 : function(){
				console.log(this.a); //10
			} 
		}
		obj7.f7();
	}
</script>
複製程式碼

這裡我們完全可以確定,就算事件中,this的指向還是遵循一個原則,誰呼叫指向誰 + 就近原則;

事件後面跟的是一個匿名函式,而呼叫這個匿名函式的則是事件中的元素,所以自然是指向了呼叫事件的元素了,實則上面的示例無不在佐證這一原則

好了,關於this的指向性問題,就講到這裡了,還是那句話,誰呼叫指向誰 + 就近原則 ,萬般皆虛幻,透過現象看本質,才能正真知道this指向的是誰


原創不易,總結不易,手打不易,轉載時請註明出處,謝謝

相關文章