(雞湯文)這一次我終於搞懂了 JavaScript 定時器的 this 指向!

隱逸王發表於2021-06-20

開篇語

忽然有一種感覺,每次學習一個知識點就像是談一場戀愛:從初次邂逅,到彼此瞭解,一切都那麼的符合戀愛的過程!

如果這個知識點再有點”調皮“的話,那簡直是讓人慾仙欲死而又不可自拔!因為你永遠不知道它還有多少面紗等著你揭開,當你自以為對它已經足夠了解的時候,冷不防就是一個盲點迎面砸來。

它簡直就像一個”寶藏女孩“,你要時刻做好迎接”驚喜“的準備!

可能正是因為這種新鮮感,我才能一直保持一種類似亢奮的狀態吧。當然,這只是針對知識而言,對待情感我還是很保守很專一的<( ̄︶ ̄)>

寶藏女孩

這兩天,我就在和定時器談戀愛,哦不,是在學習定時器( ̄▽ ̄)~*,可沒想到,又給陷進去了……

這不,上一篇文章寫完定時器的返回值後,剛覺得自己對它已經瞭解的清清楚楚明明白白了,夠我炫耀一陣子了,誰成想,喘口氣的功夫,它又給我整出了么蛾子。

惑起

寫完上篇文章後,我就琢磨著裡面的實現程式碼還可以優化一下,於是給改成了下面這個樣子:

<form action="" class="example-form">
    <div>
        <label for="name">
            名稱
        </label>
        <input class="input-ele" type="text" name="name" id="name" placeholder="please input your name"
            autocomplete="off">
    </div>
    <div style="margin-top:50px;">
        <label for="res">
            輸入
        </label>
        <textarea class="input-ele" type="multipart" name="res" id="res" readonly
            placeholder="這裡是每一次輸入的結果"></textarea>
    </div>
</form>

<script>
    window.onload = function () {
        const resEle = document.querySelector("#res");
        function changeOutputVal() {
            resEle.value += `\n${ this.value }`;
        }
        function throttle(fun, delay) {
            let last, deferTimer
            return function () {
                let now = Date.now();
                if (last && now < last + delay) {
                    clearTimeout(deferTimer);
                    deferTimer = setTimeout(function () {
                        last = now;
                        fun.apply(this);
                    }, delay)
                } else {
                    last = now;
                    fun.apply(this);
                }
            }
        }
        const inputEle = document.querySelector("#name");
        inputEle.addEventListener("input", throttle(changeOutputVal, 1000));
    }
</script>

我的修改依據是:

  1. throttle 方法返回的是一個匿名函式,這個函式正好充當 input 事件的回撥函式
  2. input 事件回撥函式中的 this 指向的是 inputEle
  3. 匿名函式中將 this 繫結給了 fun 引數,而實際使用中傳入的是 changeOutputVal 方法
  4. 所以 changeOutputVal 方法中的 this 指的就是 inputEle,所以在它裡面可以通過 this.value 獲取到 inputEle 的值

看,這邏輯多嚴謹,簡直頭頭是道啊 \( ̄︶ ̄)/

按理說,是沒問題的吧,結果卻出問題了。欲知詳情,請看大螢幕:

錯誤結果

這個 undefined 是什麼鬼?!從哪冒出來的?難道我的延時器沒用對?

解惑

面對我的質疑,setTimeout 理直氣壯地說:人家回撥函式中的 this 本來就是指向 window 物件的嘛,你也沒早問啊!

那麼,問題來了:為什麼延時器中的 this 指向的是 window 呢?setTimeout 自己也解釋不清楚了。

得,看來前人誠不我欺也——自己動手,豐衣足食!

凡事不決找 MDN,絕對靠譜!我們來看看 MDN 怎麼說:

setTimeout()呼叫的程式碼執行在與所在函式完全分離的執行環境上。這會導致,這些程式碼中包含的 this 關鍵字在非嚴格模式會指向 window (或全域性)物件,嚴格模式下為 undefined,這和所期望的this的值是不一樣的。

看到這個解釋,我才明白:this 指向 window 物件,原來是因為執行環境的不同導致的。

在上面的程式碼中,因為 window 物件沒有 value 這個屬性,所以 window.value = undefined

感覺自己在專業的方向上又邁進了一小步,容我小小地嘚瑟一下!

嘚瑟

改錯

既然知道問題出在哪,那就好辦了,我們只需要將 setTimeout 回到函式內部的 this 指向改變一下就好,這裡有以下方案。

使用變數引用外部 this

關鍵程式碼如下:

window.onload = function () {
    // some code here
    
    const that = this;
    deferTimer = setTimeout(function () {
        last = now;
        fun.apply(that);
    }, delay)
    
    // some code here
}

使用箭頭函式

利用箭頭函式不會改變 this 的指向的特性,改造如下:

window.onload = function () {
    // some code here
    
    deferTimer = setTimeout(() => {
        last = now;
        fun.apply(this);
    }, delay)
    
    // some code here
}

結束語

知錯能改,善莫大焉!

寫到這裡,我居然體會到了古人那種”朝聞道,夕死可矣“的滿足感。

在程式設計這條路上,可能遍佈荊棘,但只要我們勤耕不輟,總能開闢出屬於自己的康莊大道!

這雞湯太美味,我先乾為敬,你們隨意!b( ̄▽ ̄)d

~

~
本文完,感謝閱讀!

~

學習有趣的知識,結識有趣的朋友,塑造有趣的靈魂!

我是〖程式設計三昧〗的作者 隱逸王,我的公眾號是『程式設計三昧』,歡迎關注,希望大家多多指教!

你來,懷揣期望,我有墨香相迎! 你歸,無論得失,唯以餘韻相贈!

知識與技能並重,內力和外功兼修,理論和實踐兩手都要抓、兩手都要硬!

相關文章