記得剛開始,我理解 this
的時候 也是雲裡霧裡的,哈哈,希望通過這篇文章,對你有幫助吧。
關於 this
最多的說法,就是:誰呼叫它,this就指向誰。這話呢,不能說它錯了,只能說它講的不嚴謹,為什麼呢?我們先來了解下 this
的幾種繫結規則。
一、預設繫結
預設繫結 發生在全域性環境 。
全域性環境中,this
預設繫結到 window
。(嚴格模式下一樣)
console.log(this === window);
//true
"use strict"; //使用嚴格模式執行程式碼
var a = 666;
console.log(this.a)
//666
複製程式碼
二、隱式繫結
隱式繫結 發生在 方法呼叫(執行)。
什麼是方法呢?通常把 物件的屬性值 是函式的,稱為方法。
var obj = {
fun: function(){}
}
//這裡obj 物件裡 的fun 屬性值是個函式, 就把 這個fun 屬性稱為方法了。
複製程式碼
通常,方法呼叫時,this
隱式繫結到其 所屬的物件 上。
var a = "window";
var obj = {
a: "obj",
fun: function(){
console.log(this.a);
}
}
obj.fun();
//"obj"
複製程式碼
來個難一點的:
var obj1 = {
a: "obj1",
obj2: {
a: "obj2",
fun: function(){
console.log(this.a);
}
}
}
obj1.obj2.fun();
//"obj2"
複製程式碼
這裡的答案 跟你想的一樣嗎? 出現這個答案的關鍵 就是於,fun
函式 在作為物件方法執行時, this
繫結的是它的 所屬物件,也就是 obj2
。 而非外層 的 obj1
。
三、隱式繫結丟失
在判斷是否是隱式繫結的時候,最容易出問題的地方就是發生在 隱式繫結丟失。
隱式丟失是指被隱式繫結的函式丟失繫結物件,從而繫結到window。這種情況容易出錯卻又常見。(嚴格模式下,繫結到undefined)
隱式繫結丟失 一般 發生在 函式獨立呼叫時。
啥是獨立呼叫呢?就是一個簡單的函式執行. 函式名的前面沒有任何引導內容。
function ff(){};
ff(); //獨立呼叫
複製程式碼
當函式獨立呼叫(執行)時,this
就會隱式繫結丟失,而繫結到 window
物件上。(非嚴格模式下)
function fun(){
console.log(this === window);
}
fun();
//true
複製程式碼
那麼嚴格模式下呢?是指向 undefined
的。
function fun(){
"use strict"; //使用嚴格模式執行程式碼
console.log(this);
}
fun();
//undefined
複製程式碼
考考你:
var a = "window";
var obj = {
a: "obj",
fun1: function(){
console.log(this.a);
}
}
var fun2 = obj.fun;
fun2();
//"window"
複製程式碼
判斷是否隱式繫結丟失的關鍵就在於, 判斷函式 是否是哪種呼叫。
上面的例子,關鍵點就在 最後兩行程式碼。 先看其中的第一行:
var fun2 = obj.fun;
複製程式碼
這裡把 obj 的fun 方法 賦值給了 一個變數 fun2,這裡的 fun 並沒有作為物件的方法來執行,因為,fun 方法這裡沒有執行。
其後:
fun2();
複製程式碼
再執行 fun2,它儲存著 fun1 方法,這時候執行 fun2(等於fun1) ,但是,它是獨立呼叫。因為,沒有作為物件的方法來呼叫。所以 this 就被指向 window了。
那麼怎麼解決隱式丟失問題呢?
var a = "window";
var obj = {
a: "obj",
fun: function(){
return function(){
console.log(this.a);
}
}
}
obj.fun()();
//"window"
//這裡我們想要的是 obj裡 a 的值,可是,隱式繫結丟失導致獲取到了 window 裡 a 的值。
複製程式碼
可以基礎好的已經知道答案了:
var a = "window";
var obj = {
a: "obj",
fun: function(){
var that = this;
return function(){
console.log(that.a);
}
}
}
obj.fun()();
//"obj"
複製程式碼
因為 fun
是作為方法呼叫的,所以 this
繫結到 obj
物件上,因此,我們就可以先用一個變數 that
來儲存 this
,然後 在內部的 匿名函式中 使用 that
就可以了。它儲存著 上面 this
的繫結物件。
忽然靈機一動,想出個題目,看人家都玩出題,我也試試,哈哈:
var a = "window";
function fun(){
var a = "fun";
return (function(){
return this.a;
})()
}
fun()
//“你猜”
複製程式碼
這道題中 有立即執行函式、this問題,哈哈,乍一看挺噁心,其實看完上面的,應該可以看出來的。
四、顯示繫結
通過call()、apply()、bind()方法把 this
繫結到物件上,叫做顯式繫結。對於被呼叫的函式來說,叫做間接呼叫。
var a = "window"
var obj = {
a:"obj",
}
function fun(){
console.log(this.a);
}
fun.call(obj);
//"obj";
複製程式碼
這裡使用了 call 方法,把fun 中的 this 繫結到 obj 物件上。
javascript內建的一些函式,具有顯式繫結的功能,如陣列的5個迭代方法:map()、forEach()、filter()、some()、every(),以及建立物件的 Object.create() 函式(後面原型鏈中會細說),都可以手動繫結this。
大家可以去看下API文件,陣列的這幾個函式的最後一個引數,就是指定函式內 this 的繫結,如果不指定,則是window
,嚴格模式下是undefined
。
var a = [1,2,3];
a.forEach(function(){
console.log(this)
},a);
//繫結 this 為 a 這個陣列
//(3) [1, 2, 3]
//(3) [1, 2, 3]
//(3) [1, 2, 3]
複製程式碼
五、new 繫結
如果 使用 new
來建立物件,因為 後面跟著的是建構函式,所以稱它為構造器呼叫。對於this
繫結來說,稱為new
繫結。
想知道 構造器呼叫 中 this
的繫結,就要知道 new
到底做了啥了。
先來個 new 的實現。看不懂不要緊,在後面原型鏈那篇,還會說的。
function New(proto){ //proto 為傳進來的建構函式
var obj = {};
obj.__proto__ = proto.prototype;
proto.apply(obj, Array.prototype.slice.call(argument,1));
//你這要看懂這步就行。這裡把建構函式裡的 this 繫結到了 新的obj 物件上,最後 返回了該新物件,作為例項物件。
return obj;
}
複製程式碼
所以在使用 new 來建立例項物件時,new 內部把 建構函式的 this 繫結到 返回的新物件 上了。
function Person(name){
this.name = name;
}
var c = new Person("zdx");
c.name;
複製程式碼
總結: this的四種繫結規則:隱式繫結、隱式繫結丟失、顯式繫結和new繫結,分別對應函式的四種呼叫方式:方法呼叫、獨立呼叫、間接呼叫和構造器呼叫。
附錄:
1、關於this繫結 的優先順序問題。
簡單提一下吧:
new 繫結 > 顯示繫結 > 隱式繫結 > 預設繫結 。
2、ES6 中,箭頭函式的 this 繫結。
箭頭函式內的 this 繫結的 是所屬的環境(函式或者物件), 它是固定不變的。
先看下上面的這個例子
var a = "window";
var obj = {
a: "obj",
fun: function(){
return function(){
console.log(this.a);
}
}
}
obj.fun()();
//"window"
複製程式碼
上面我們使用 一個變數來儲存 this 的繫結,下面我們來用 箭頭函式解決問題
var a = "window";
var obj = {
a: "obj",
fun: function(){
return () => {
console.log(this.a);
}
}
}
obj.fun()();
//"obj"
複製程式碼
實際上,箭頭函式內部是沒有this
的,所以,它不能使用 new
構造器呼叫,call
等顯示繫結。所以它內部就是使用了一個變數來儲存 箭頭函式 所屬環境的(函式或者物件) this
。
就相當於:
var a = "window";
var obj = {
a: "obj",
fun: function(){
that = this;
return function(){
console.log(that.a);
}
}
}
obj.fun()();
//"obj"
複製程式碼
考考你:
var a = "window";
var obj = {
a: "obj",
fun1: function(){
return () => {
console.log(this.a);
}
}
}
var fun2 = obj.fun1;
fun2()();
複製程式碼