ES6學習筆記之 this 詳解


1.非箭頭函數下的 this 

var obj = {
            x: 0,
            f1: function () {
                console.log(this.x);
            }
        }
        var f1 = obj.f1;
        var x = 1;

        obj.f1(); //0
        f1(); //1

上面代碼中,雖然 obj.f1 和 f1 指向的是同一個函數,但是執行的結果卻不一樣。這種差異的原因,就在於函數體內使用了 this 關鍵字。我們都知道,this 指的是函數運行時所在的環境。對於 obj.f1() 來說,f1 運行在 obj 環境下,所以this 指向obj ;對於 f1() 來說,f1() 運行在全局環境,所以 this 指向全局環境。因此,兩者的運行結果不一樣。

現在我們來想想,為什么會這樣呢?為什么obj.f1() 就是在 obj 環境執行,而 var f1 = obj.f1 之后,f1() 就是在全局環境執行呢?

那就要從內存里面的數據結構說起了,我們一步一步來看。

var obj = {
            x: 0,
            f1: function () {
                console.log(this.x);
            }
        }

上面代碼中,將一個對象賦值給變量 obj ,javascript 引擎會先在內存中,生成一個對象,然后再將這個對象的內存地址賦值給變量 obj。也就是說,變量obj 是一個地址。后面如果要讀取 obj.x,引擎首先從 obj 拿到內存地址,然后再從該地址讀出原始的對象,返回 x 屬性。


如上圖所示,對象的每一個屬性名都對應一個屬性描述對象,屬性的值保存在屬性描述對象的 value 屬性里面。

屬性的值為函數時,引擎會將函數單獨保存在內存中,然后再將函數的地址賦值給 f1 屬性的 value 屬性。由於函數是一個單獨的值,所以它可以在不同的環境(上下文)執行

我們可以在函數內部,引用當前環境的其他變量。那么,是怎樣在函數內部獲得當前的運行環境的呢?答案就是 this ,this 的設計目的就是在函數體內部,指代函數當前的運行環境。

現在我們可以回答一開始的問題了, obj.f1() 是通過 obj 里存的地址找到 f1, 所以是在 obj 環境下執行的;而 var f1 = obj.f1 之后,f1() 直接指向函數本身,所以 f1() 就是在全局環境執行.

2.箭頭函數中的this

箭頭函數不會創建自己的this,它只會從自己的作用域鏈的上一層繼承this。箭頭函數導致 this 總是指向函數定義生效時所在的對象。

function Timer(){
    this.s1 = 0;
    this.s2 = 0;
    //箭頭函數
    setInterval(() => this.s1++, 1000);
    //普通函數
    setInterval(function (){
        this.s2++;
    }, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);  //s1:  3
setTimeout(() => console.log('s2: ',timer.s2), 3100);   //s2:  0

上面代碼中, Timer 函數內部設置了兩個定時器,分別使用了箭頭函數和普通函數。箭頭函數的 this 綁定定義時所在的作用域(即 Timer 函數),普通函數的 this 指向運行時所在的作用域(即全局對象)。var timer = new Timer() 這條語句執行之后,再過1000毫秒,箭頭函數定義生效,this總是指向函數定義生效時所在對象,即timer;對於普通函數,因為setTimeout()調用的代碼運行在與所在函數完全分離的執行環境上,這會導致 this 指向 window 對象。所以3100毫秒之后,timer.s1 被更新了3次,而 timer.s2 一次都沒更新。

箭頭函數中,this 對象的指向是固定的。這種指向的固定化,實際是因為箭頭函數根本沒有自己的 this ,導致內部的 this 就是外層代碼塊的 this 。正是因為它沒有this,所以也就不能用作構造函數。

        var name = 'window';
        var A = {
            name : 'A',
            show: () => {
                console.log(this.name);
            }
        }
        A.show();  //window

如上面代碼中的箭頭函數,即 show 函數內沒有 this,其內部的 this 就是外層代碼塊的 this,因為沒有其他函數的包裹,所以最外層代碼塊的this指向的就是window對象。所以最后會輸出window。

那么,怎樣改成永遠綁定A呢?

        var name = 'window';
        var A = {
            name: 'A',
            show: function() {
                var s = () => console.log(this.name);
                return s;
            }
        }
        var show = A.show();
        show(); //A
        var B = {
            name: B
        }
        show.call(B);   //A
        show.call();    //A

這樣就做到了永遠指向 A 對象,我們再來分析一下:

原代碼中 show 即是箭頭函數,沒有自己的 this;修改之后的代碼中,箭頭函數所在的作用域是在 show 函數內,show 是普通函數,有自己的 this,在函數調用時生效。A.show() 指向的對象是 A。所以箭頭函數 s 中的 this 就是指向 A 了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM