DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> JavaScript基礎知識 >> 關於setTimeout的妙用前端函數節流
關於setTimeout的妙用前端函數節流
編輯:JavaScript基礎知識     

      最近在某團隊忙於一個項目,有這麼一個頁面,采用傳統模式開發(吐槽它為什麼不用React),它的DOM操作比較多,然後性能是比較差的,尤其當你縮放窗口時,可怕的事情發生了,出現了卡頓,甚至浏覽器癱瘓。為什麼呢?

      由於該頁面的DOM操作非常多,故窗口縮放每一幀時都會觸發函數的執行,連續的重新DOM操作,這樣對浏覽器的開銷是非常大的。既然在窗口縮放時,會讓浏覽器重新計算DOM,那麼我們為什麼不可以讓DOM的計算延時呢,讓窗口停止縮放後才重新計,這樣不就節省了浏覽器的開銷,達到優化的效果了嗎?

 

知識准備

1. setTimeout(code,millisec) 當然就是本文的主角了。

setTimeout() 方法用於在指定的毫秒數後調用函數或計算表達式。

code必需。要調用的函數後要執行的 JavaScript 代碼串。

millisec必需。在執行代碼前需等待的毫秒數。

提示:setTimeout() 只執行 code 一次。如果要多次調用,請使用 setInterval() 或者讓 code 自身再次調用 setTimeout()。

廣泛應用於定時器,輪播圖,動畫效果,自動滾動等等。

 

2. clearTimeout(id_of_setTimeout)

參數 id_of_settimeout由 setTimeout() 返回的 ID 值。該值標識要取消的延遲執行代碼塊。

 

3. fun.apply(thisArg[, argsArray])

apply() 方法在指定 this 值和參數(參數以數組或類數組對象的形式存在)的情況下調用某個函數

該函數的語法與call()方法幾乎相同,唯一的區別在於,call()方法接受的是一個參數列表,而apply()接受的是一個包含多個參數數組的(或類數組對象)。

參數

thisArg

在 fun 函數運行時指定的 this 值。需要注意的是,指定的 this 值並不一定是該函數執行時真正的 this 值,如果這個函數處於非嚴格模式下,則指定為 null 或 undefined 時會自動指向全局對象(浏覽器中就是window對象),同時值為原始值(數字,字符串,布爾值)的 this 會指向該原始值的自動包裝對象。

argsArray

一個數組或者類數組對象,其中的數組元素將作為單獨的參數傳給 fun 函數。如果該參數的值為null 或 undefined,則表示不需要傳入任何參數。從ECMAScript 5 開始可以使用類數組對象。

在調用一個存在的函數時,你可以為其指定一個 this 對象。 this 指當前對象,也就是正在調用這個函數的對象。 使用 apply, 你可以只寫一次這個方法然後在另一個對象中繼承它,而不用在新對象中重復寫該方法。

 

4. fun.call(thisArg[, arg1[, arg2[, ...]]])

該 方法在使用一個指定的this值和若干個指定的參數值的前提下調用某個函數或方法.

 

參數

thisArg

在fun函數運行時指定的this值。需要注意的是,指定的this值並不一定是該函數執行時真正的this值,如果這個函數處於非嚴格模式下,則指定為null和undefined的this值會自動指向全局對象(浏覽器中就是window對象),同時值為原始值(數字,字符串,布爾值)的this會指向該原始值的自動包裝對象。

arg1, arg2, ...

指定的參數列表。

 

當調用一個函數時,可以賦值一個不同的 this 對象。this 引用當前對象,即 call 方法的第一個參數。通過 call 方法,你可以在一個對象上借用另一個對象上的方法,比如Object.prototype.toString.call([]),就是一個Array對象借用了Object對象上的方法。

 

作用:

使用call方法調用父構造函數

使用call方法調用匿名函數

使用call方法調用匿名函數並且指定上下文的'this'

 

這裡插個題外話:

apply 與 call() 非常相似,不同之處在於提供參數的方式。apply 使用參數數組而不是一組參數列表。apply 可以使用數組字面量(array literal),如 fun.apply(this, ['eat', 'bananas']),或數組對象, 如  fun.apply(this, new Array('eat', 'bananas'))。你也可以使用 arguments  對象作為 argsArray 參數。 arguments 是一個函數的局部變量。 它可以被用作被調用對象的所有未指定的參數。 這樣,你在使用apply函數的時候就不需要知道被調用對象的所有參數。 你可以使用arguments來把所有的參數傳遞給被調用對象。 被調用對象接下來就負責處理這些參數。

 

從 ECMAScript 第5版開始,可以使用任何種類的類數組對象,就是說只要有一個 length 屬性和[0...length) 范圍的整數屬性。例如現在可以使用 NodeList 或一個自己定義的類似 {'length': 2, '0': 'eat', '1': 'bananas'} 形式的對象。

 

call, apply方法區別是,從第二個參數起, call方法參數將依次傳遞給借用的方法作參數, 而apply 直接將這些參數放到一個數組中再傳遞, 最後借用方法的參數列表是一樣的.

應用場景:當參數明確時可用call, 當參數不明確時可用apply給合arguments

 

現在先給出一個例子

總所皆知,onscolll,onresize等是非常耗性能,窗口縮放時打印數字。

 var count = 0;
 window.onresize = function () {
     count++;
     console.log(count);
 }

在chrome浏覽器中伸縮浏覽器窗口大小,打印如下

這顯然不是我們想要的,那如果我們換成ajax請求的話,那麼就會縮放一次窗口會連續觸發多次ajax請求,下面我們試著使用函數節流的操作試試一下;當然加個settimeout()的定時器就好了,

 

第一種封裝方法

 var count = 0;
 function oCount() {
     count++;
     console.log(count);
 }
 window.onresize = function () {
     delayFun(oCount)
 };
 
 function delayFun(method, thisArg) {
     clearTimeout(method.props);
     method.props = setTimeout(function () {
         method.call(thisArg)
     }, 200)
 }

 

第二種封裝方法

構造一個閉包,使用閉包的方式形成一個私有的作用域來存放定時器timer, timer是通過傳參數的形式引入的。

 var count = 0;
 function oCount() {
     count++;
     console.log(count);
 }
 var funs= delayFun(oCount,100);
 window.onresize = function () {
    funs()
 };
 
 function delayFun(func, wait) {
     var timer = null;
     return function () {
         var context = this,
                 args = arguments;
         clearTimeout(timer);
         timer = setTimeout(function () {
             func.apply(context, args);
         }, wait)
     };
 }

 

對第二種方法優化一下,性能會更好

這裡返回一個函數,如果它被不間斷地調用,它將不會得到執行。該函數在停止調用 N 毫秒後,再次調用它才會得到執行。如果有傳遞 ‘immediate’ 參數,會馬上將函數安排到執行隊列中,而不會延遲。

 function delayFun (func, wait, immediate) {
     var timeout;
     return function() {
         var context = this, args = arguments;
         var later = function() {
             timeout = null;
             if (!immediate) func.apply(context, args);
         };
         var callNow = immediate && !timeout;
         clearTimeout(timeout);
         timeout = setTimeout(later, wait);
         if (callNow) func.apply(context, args);
     };
 };
 
 // 用法
 var myEfficientFn = delayFun (function() {
         // 所有繁重的操作
 }, 250);
 window.addEventListener('resize', myEfficientFn);

函數不允許回調函數在指定時間內執行多於一次。當為一個會頻繁觸發的事件分配一個回調函數時,該函數顯得尤為重要。

 

setTimeout這麼厲害,那麼我們是可以在項目中大量使用嗎?

我個人是不建議的,在我們業務中,基本上是禁止在業務邏輯中使用setTimeout的,因為我所看到的很多使用方式都是一些問題不好解決,setTimeout作為一個hack的方式。

例如,當一個實例還沒有初始化的前,我們就使用這個實例,錯誤的解決辦法是使用實例時加個setTimeout,確保實例先初始化。

為什麼錯誤?這裡其實就是使用hack的手段

第一是埋下了坑,打亂模塊的生命周期

第二是出現問題時,setTimeout其實是很難調試的。

我認為正確的使用方式是,看看生命周期(可參考《關於軟件的生命周期 》),把實例化提到使用前執行。

 

參考:

7 個基本的 JS 函數http://web.jobbole.com/82540/

《關於軟件的生命周期 》

 

打廣告:要找實習了,有沒有老板要收留我。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved