舉個栗子,咱們知道目前的一種說法是當 1 秒內連續播放 24 張以上的圖片時,在人眼的視覺中就會造成一個連貫的動畫,因此在電影的播放(之前是,如今不知道)中基本是以每秒 24 張的速度播放的,爲何不 100 張或更可能是由於 24 張就能夠知足人類視覺需求的時候,100 張就會顯得很浪費資源。再舉個栗子,假設電梯一次只能載一人的話,10 我的要上樓的話電梯就得走 10 次,是一種浪費資源的行爲;而實際生活正顯然不是這樣的,當電梯裏有人準備上樓的時候若是外面又有人按電梯的話,電梯會再次打開直到滿載位置,從電梯的角度來講,這時一種節約資源的行爲(相對於一次只能載一我的)。ajax
這裏以判斷頁面是否滾動到底部爲例,普通的作法就是監聽 window
對象的 scroll
事件,而後再函數體中寫入判斷是否滾動到底部的邏輯:瀏覽器
function onScroll() { // 判斷是否滾動到底部的邏輯 const pageHeight = $('body').height(); const scrollTop = $(window).scrollTop(); const winHeight = $(window).height(); const thresold = pageHeight - scrollTop - winHeight; if (thresold > -100 && thresold <= 20) { console.log('end'); } } $(window).on('scroll', onScroll);
這樣作的一個缺點就是比較消耗性能,由於當在滾動的時候,瀏覽器會無時不刻地在計算判斷是否滾動到底部的邏輯,而在實際的場景中是不須要這麼作的,在實際場景中多是這樣的:在滾動過程當中,每隔一段時間在去計算這個判斷邏輯。而函數節流所作的工做就是每隔一段時間去執行一次本來須要無時不刻地在執行的函數,因此在滾動事件中引入函數的節流是一個很是好的實踐:服務器
$(window).on('scroll', throttle(onScroll));
加上函數節流以後,當頁面再滾動的時候,每隔 300ms
纔會去執行一次判斷邏輯。閉包
簡單來講,函數的節流就是經過閉包保存一個標記(canRun = true
),在函數的開頭判斷這個標記是否爲 true
,若是爲 true
的話就繼續執行函數,不然則 return 掉,判斷完標記後當即把這個標記設爲 false
,而後把外部傳入的函數的執行包在一個 setTimeout
中,最後在 setTimeout
執行完畢後再把標記設置爲 true
(這裏很關鍵),表示能夠執行下一次的循環了。當 setTimeout
還未執行的時候,canRun
這個標記始終爲 false
,在開頭的判斷中被 return 掉。app
function throttle(fn, interval = 300) { let canRun = true; return function () { if (!canRun) return; canRun = false; setTimeout(() => { fn.apply(this, arguments); canRun = true; }, interval); }; }
這裏以用戶註冊時驗證用戶名是否被佔用爲例,現在不少網站爲了提升用戶體驗,不會再輸入框失去焦點的時候再去判斷用戶名是否被佔用,而是在輸入的時候就在判斷這個用戶名是否已被註冊:函數
$('input.user-name').on('input', function () { $.ajax({ url: `https://just.com/check`, method: 'post', data: { username: $(this).val(), }, success(data) { if (data.isRegistered) { $('.tips').text('該用戶名已被註冊!'); } else { $('.tips').text('恭喜!該用戶名還未被註冊!'); } }, error(error) { console.log(error); }, }); });
很明顯,這樣的作法很差的是當用戶輸入第一個字符的時候,就開始請求判斷了,不只對服務器的壓力增大了,對用戶體驗也未必比原來的好。而理想的作法應該是這樣的,當用戶輸入第一個字符後的一段時間內若是還有字符輸入的話,那就暫時不去請求判斷用戶名是否被佔用。在這裏引入函數防抖就能很好地解決這個問題:post
$('input.user-name').on('input', debounce(function () { $.ajax({ url: `https://just.com/check`, method: 'post', data: { username: $(this).val(), }, success(data) { if (data.isRegistered) { $('.tips').text('該用戶名已被註冊!'); } else { $('.tips').text('恭喜!該用戶名還未被註冊!'); } }, error(error) { console.log(error); }, }); }));
其實函數防抖的原理也很是地簡單,經過閉包保存一個標記來保存 setTimeout
返回的值,每當用戶輸入的時候把前一個 setTimeout
clear 掉,而後又建立一個新的 setTimeout
,這樣就能保證輸入字符後的 interval
間隔內若是還有字符輸入的話,就不會執行 fn
函數了。性能
function debounce(fn, interval = 300) { let timeout = null; return function () { clearTimeout(timeout); timeout = setTimeout(() => { fn.apply(this, arguments); }, interval); }; }
其實函數節流與函數防抖的原理很是簡單,巧妙地使用 setTimeout
來存放待執行的函數,這樣能夠很方便的利用 clearTimeout
在合適的時機來清除待執行的函數。動畫
使用函數節流與函數防抖的目的,在開頭的栗子中應該也能看得出來,就是爲了節約計算機資源。網站