進擊的 JavaScript(六) 之 this

周大俠啊發表於2018-07-31

周大俠啊 進擊的 JavaScript(六) 之 this

記得剛開始,我理解 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()();
複製程式碼

相關文章