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

開篇語

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

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

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

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

寶藏女孩

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

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

惑起

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

<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

~

~

~ 本文完,感謝閱讀!

~

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

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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章