2016-08-15 64 views
3

有誰知道什麼可以導致傳遞到setTimeout(func, 0)被調用的函數顯着延遲?從setTimeout(func, 0)執行時間和func實際調用時間之間,我看到延遲超過1.2秒。什麼可以導致從setTimeout回調延遲(回調,0)

我只看到這種行爲與Chrome v52(v51沒有它)。所以,這可能是一個Chrome的問題,但在我報告它是一個錯誤之前,我想調查它。

我不幸沒有一個可以重新使用它的小案例。它發生在AngularJS應用程序中,以響應滾動事件。我們通過鼠標滾輪來處理滾動事件觸發器,並且作爲其中的一部分,請調用setTimeout(func, 0)來完成一些後續工作。

使用Chrome devtools,我可以看到在這個1.2秒的差距期間只有很少的JavaScript運行。我沒有阻止線程。事實上,我使用console.profile和console.profileEnd來分析特定的1.2秒圖像。它顯示它在99%的時間裏處於閒置狀態。

看devtools中的時間線,我在那個時間段內看到的唯一一件事就是處理「Mouse Wheel」事件。他們中的很多人(差距的前1秒每5毫秒一次)。這本身有點奇怪,因爲在那段時間鼠標滾輪沒有滾動。我做了一個Mac Magic Mouse的虛擬滾輪的輕拂,我們撞到了可滾動區域的頂部。事件發生後,我們不斷收到鼠標滾輪事件。

有沒有人有任何想法?可以很快執行一小段鼠標滾輪事件來延遲迴調的調用嗎?有什麼方法可以調查爲什麼超時回調在其調度時間後1.2秒被調用?

編輯:添加了描繪setTimeout顯示回調的時間線圖像。

Timeline of slow setTimeout

+1

你肯定需要一個可複製的例子來報告它的錯誤,所以請不要猶豫,發佈它。 – estus

+0

'setTimeout(func,0)'將函數放在隊列中,並調度它以調用下一個事件循環或下一個「記號」,換句話說。由於瀏覽器僅爲UI使用單個線程,因此可能會造成一些問題。鼠標滾輪事件可能是相關的,但我不認爲有一個明確的方式來說明沒有檢查他們在做什麼和爲什麼。 – vlaz

+0

我添加了一個顯示時間線的圖像。控制檯正在記錄何時調用'setTimeout'方法以及何時執行回調。在這個例子中,它需要超過1.4秒。摘要圖顯示我們大多數時間都處於閒置狀態。它真的可能是有人在竊取線程嗎? – Steven

回答

-1

這裏發佈到包括圖像

的setTimeout(FUNC,0),而您的代碼運行的保證,在0秒,如果有已經在任務列隊事件隊列(最有可能通過滾動觸發),那麼他們將不得不完成被放入執行堆棧並運行。

檢查從https://www.youtube.com/watch?v=8aGhZQkoFbQ

event loop and task queue

如果您需要立即執行這一形象,你需要確保在你的滾動頁面時任務隊列中添加任何情況下,這意味着沒有事件監聽器如.on('scroll', func)或在註冊func之前執行setTimeout .on('scroll')

+0

我可以非常明白,它不能保證在0秒運行,但我希望它會比1.2秒後運行得更快。 我已將圖像添加到顯示事件發生時間表的原始帖子。我添加了控制檯輸出來標記時間線,以及執行setTimeout調用的時間以及何時獲得回調。在這個例子中,它花費了1.4秒。 摘要圖還顯示我們在等待回調的大部分時間都處於閒置狀態。那麼,真的可能是另一個阻礙我們的事件嗎? – Steven

+0

我不知道爲什麼我放棄了投票,但你問「能夠很快執行的一小段鼠標滾輪事件會延遲迴調的調用嗎?」答案很可能是肯定的,這取決於onscroll上回調的內容是什麼 – David

1

setTimeout(..., 0)將事件放入事件隊列中。但它不會優先於已經放入隊列的其他事件。當然,當你處理滾動事件時,你將會遇到許多滾動事件被放入隊列的情況,而滾動事件處理程序代碼仍在運行,它正在處理前一個滾動事件。

所以,事實上,當處理滾動事件時,你會很容易陷入延誤。當您的調用堆棧爲空時,即當前正在運行的事件處理程序完成時,將從事件隊列中獲取下一個事件。即使在某個隊列中有一個與超時相關的事件,它也不會獲得更高的優先級:所有事件都必須輪候。

例如,如果隊列中已經有10個滾動事件,它們將全部被處理,並且當鼠標仍在滾動時,問題只會變得更糟,因爲它可以添加更多的事件到隊列,並以更高的速度讓您的代碼可以處理它們。

解決這個問題的一種方法是讓你的滾動事件處理程序儘可能輕鬆,並且異步執行繁重的任務。如果處理程序檢測到在上一次運行中已經有計劃以這種方式運行,它可能會簡單地取消該計劃任務,並將其替換爲新版本。

示例代碼:

var task = -1; 
window.addEventListener('scroll', function(e) { 
    clearTimeout(task); 
    task = setTimeout(function() { 
     // all the (heavy) processing related to scrolling comes here. 
    }, 0); 
}; 

setTimeout不被從問題的一個困惑。但請注意,當您啓動另一個setTimeout時,它將按順序處理。上面的代碼只是將實際處理移動到隊列的末尾。但是如果有更多的滾動事件,他們將每個刪除來自隊列的超時事件,並且在隊列的處新增一個。

這樣做的效果是,實際上滾動處理程序代碼的大部分執行次數會減少,這可能會對某些動畫的流動性產生一些負面影響。但它會阻止滯後,並且作爲獎勵,您的其他setTimeout事件將盡快結束。

+0

*「......一個[滾動事件]每間隔5秒大約差距的前1秒......」*:即200個滾動事件在1秒內。*「在此期間鼠標滾輪不滾動」:這隻能意味着JavaScript無法跟上事件隊列,無論開發工具如何。 – trincot

+0

這也被稱爲「debouncing」函數調用。另見https://davidwalsh.name/javascript-debounce-function –