# [Javascript] Arrow function 與一般匿名 function 對 this 的行為比較

# What is " this”

Javascript 在 ES6 之後迎來了一個新的 function 表示方法 — Arrow function。

其表示方式比起一般的 function 表示方式更為簡潔。我們可以使用以下撰寫形式表示以往的 function 行為。

(arg1, arg2, ...) => { /* code here */ }

當 Arrow function 的 argument 只有一個時我們可以省略 () ,當內容指有一項,並且要作為回傳時,我們甚至可以省略 {} 。綜合以上所述,我們在簡化為如下的表示。

arg1 => somethingCodeNeedReturn();

在許多的網站中,不時有看到一些介紹 Arrow function 的文章講解就類似上述所言: 我們可以將一般 function 改寫成 Arrow functionArrow function 是一般 function 的一種簡化表示方式

# 然而,真如此單純嗎?

如果這麼單純,我也不用寫這篇文章了。

其實,查詢一下 MDN 上關於 Arrow function 的描述便可以知道了,這兩者其實存在著很多的差異,之所以沒有感覺,可能純粹只是 運.氣.好

還沒有踩到坑啊!

![此段落擷取自 MDN](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/Arrow_functions)

從這段話可以理解 Arrow function 本身沒有 thissuperarguments … 等 一般 function 擁有的屬性。而今天要探討的 this 不同於 一般 function this 值取決於被呼叫的函式來決定 ; Arrow function 則是 在定義時便綁定到對象的物件上 ( 我們可以理解為 Arrow function 的 this 跟隨其物件的 this )。

我們來看一些例子可能會更清楚

在 Global 的行為

在 Global 中基本上並無差異,原因是不論是一般 function 還是 Arrow function 都該綁定到 Window,這很好理解。

在 Object 的行為

接下來從 Object 開始便有所差異了。

var obj = {
    methodA_normal: function() {
        // Object
       console.log("[ normal function ]", this.constructor.name);
       setTimeout(function() {
            // Window
            console.log("[ normal function <in timeout> normal function ]", this.constructor.name)
       }, 1);
    },
    methodA_arrow: () => {
         // Window
       console.log("[ arrow function ]", this.constructor.name);
       setTimeout(() => {
            // Window
            console.log("[ arrow function <in timeout> arrow function ]", this.constructor.name)
       }, 1);
    },
	methodA_normal_with_arrow_timeout: function() {
         // Object
       console.log("[ normal function ]", this.constructor.name);
       setTimeout(() => {
            // Object
            console.log("[ arrow function <in timeout> normal function ]", this.constructor.name)
       }, 1);
    },
	methodA_arrow_with_normal_timeout: () => {
         // Window
       console.log("[ arrow function ]", this.constructor.name);
       setTimeout(function() {
            // Window
            console.log("[ normal function <in timeout> arrow function ]", this.constructor.name)
       }, 1);
    },
}

這是執行 methodA_normal 的結果

這是執行 methodA_normal 的結果

這是執行 methodA_arrow 的結果

這是執行 methodA_arrow 的結果

這是執行 methodA_normal_with_arrow_timeout 的結果

這是執行 methodA_normal_with_arrow_timeout 的結果

這是執行 methodA_arrow_with_normal_timeout 的結果

這是執行 methodA_arrow_with_normal_timeout 的結果

從上面的例子我們可以理出一些端倪,Arrow function 中的 this 在定義時,會向外尋找掛載組件的 this 直到找到 global 為止。而一般 function 中的 this 則在使用時,視使用對象而獲得不同的 this

因此,在 methodA_normalsetTimeout 中的 function 才會得到 windowmethodA_normal_with_arrow_timeout 中的 Arrow function 則是獲得 Object

Class 生成的物件行為

class A {
    constructor() {
        this.methodA_arrow = () => {
            // A
            console.log("[ arrow function ]", this.constructor.name);
            setTimeout(() => {
                    // A
                    console.log("[ arrow function <in timeout> arrow function ]", this.constructor.name)
            }, 1);
        };
        this.methodA_arrow_with_normal_timeout = () => {
                // A
            console.log("[ arrow function ]", this.constructor.name);
            setTimeout(function() {
                    // Window
                    console.log("[ normal function <in timeout> arrow function ]", this.constructor.name)
            }, 1);
        };
    }
    methodA_normal() {
        // A
       console.log("[ normal function ]", this.constructor.name);
       setTimeout(function() {
            // Window
            console.log("[ normal function <in timeout> normal function ]", this.constructor.name)
       }, 1);
    }
	methodA_normal_with_arrow_timeout() {
         // A
       console.log("[ normal function ]", this.constructor.name);
       setTimeout(() => {
            // A
            console.log("[ arrow function <in timeout> normal function ]", this.constructor.name)
       }, 1);
    }
}

我們利用上方的 Class A new 一個 a 物件來做為測試。

var a = new A();

這是執行 methodA_arrow 的結果

這是執行 methodA_arrow 的結果

這是執行 methodA_arrow_with_normal_timeout 的結果

這是執行 methodA_arrow_with_normal_timeout 的結果

這是執行 methodA_normal 的結果

這是執行 methodA_normal 的結果

這是執行 methodA_normal_with_arrow_timeout 的結果

這是執行 methodA_normal_with_arrow_timeout 的結果

測試至此,依照上方的運作原理,其結果也就不言而喻了。

# 總結

之所以會有契機要記錄這篇文章,所有的起因皆為某天與同事們聊到 Nuxt 具有記憶體洩漏 (Memory leak) 的問題。

在查詢相關文獻時,發現了這篇文章找出問題的根源並且修正。

NUXT 内存泄漏引发问题

其中作者發現的問題為 Nuxt 在 Request 的 Closure 中,由於 從頭到尾使用 Arrow function然而 Arrow function 並沒有被綁定在任何物件上 (也就是說 Arrow function 最終綁定在 window 上) ,導致 NodeJs 無法正確地清理無用的資源,最終造成 Nuxt Memory Leak。

而作者的解決方式也很單純:將 arrow function 改為一般 function 寫法,便搞定了。

這不禁讓我反思

如果是我,是否找得出這個 Bug?

若是 沒有清楚地明瞭 Arrow function 與一般 function 的差異,我們可能真的就把 Arrow function 當成一般 function 來理解,然後就忽視了這個地方,最終當然什麼 Bug 也沒解掉。

因此,趁著這次機會,將 Arrow function 與一般 function 的比較記錄下來,以便未來需要時可以快速複習。

Like z20240z's work