我記得早期的 JavaScript ,要完成任何事情幾乎都繞不開一些簡單的函式,因為瀏覽器提供商實現功能有所差異,而且不只是邊緣功能,基礎功能也一樣,如 addEventListener
和 attachEvent
。雖然時代變了,但仍有一些函式是每個開發者都應該掌握的,以便於完成某些功能和提高效能。
debounce
對於高耗能事件,debounce 函式是一種不錯解決方案。如果你不對 scroll
、resize
、和 key*
事件使用 debounce 函式,那麼你幾乎等同於犯了錯誤。下面的 debounce
函式能讓你的程式碼保持高效:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 返回一個函式,如果它被不間斷地呼叫,它將不會得到執行。該函式在停止呼叫 N 毫秒後,再次呼叫它才會得到執行。如果有傳遞 ‘immediate’ 引數,會馬上將函式安排到執行佇列中,而不會延遲。 function debounce(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 = debounce(function() { // 所有繁重的操作 }, 250); window.addEventListener('resize', myEfficientFn); |
debounce
函式不允許回撥函式在指定時間內執行多於一次。當為一個會頻繁觸發的事件分配一個回撥函式時,該函式顯得尤為重要。
poll
儘管上面我提及了 debounce
函式,但如果事件不存在時,你就不能插入一個事件以判斷所需的狀態,那麼就需要每隔一段時間去檢查狀態是否達到你的要求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
function poll(fn, callback, errback, timeout, interval) { var endTime = Number(new Date()) + (timeout || 2000); interval = interval || 100; (function p() { // 如果條件滿足,則執行! if(fn()) { callback(); } // 如果條件不滿足,但並未超時,再來一次 else if (Number(new Date()) < endTime) { setTimeout(p, interval); } // 不匹配且時間消耗過長,則拒絕! else { errback(new Error('timed out for ' + fn + ': ' + arguments)); } })(); } // 用法:確保元素可見 poll( function() { return document.getElementById('lightbox').offsetWidth > 0; }, function() { // 執行,成功的回撥函式 }, function() { // 錯誤,失敗的回撥函式 } ); |
Polling 在 web 中已被應用很長時間了,並在將來仍會被使用。
once
有時候,你想讓一個給定的功能只發生一次,類似於 onload 事件。下面的程式碼提供了你所說的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function once(fn, context) { var result; return function() { if(fn) { result = fn.apply(context || this, arguments); fn = null; } return result; }; } // 用法 var canOnlyFireOnce = once(function() { console.log('Fired!'); }); canOnlyFireOnce(); // "Fired!" canOnlyFireOnce(); // nada // 沒有執行指定函式 |
once
函式確保給定函式只能被呼叫一次,從而防止重複初始化!
getAbsoluteUrl
從一個字串變數得到一個絕對 URL,並不是你想象中這麼簡單。對於某些 URL
構造器,如果你不提供必要的引數就會出問題(而有時候你真的不知道提供什麼引數)。下面有一個優雅的技巧,只需要你傳遞一個字串就能得到相應的絕對 URL。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var getAbsoluteUrl = (function() { var a; return function(url) { if(!a) a = document.createElement('a'); a.href = url; return a.href; }; })(); // 用法 getAbsoluteUrl('/something'); // http://davidwalsh.name/something |
a 元素的 href
處理和 url 處理看似無意義,而 return 語句返回了一個可靠的絕對 URL。
isNative
如果你想知道一個指定函式是否是原生的,或者能不能通過宣告來覆蓋它。下面這段便於使用的程式碼能給你答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
;(function() { // 用於處理傳入引數 value 的內部 `[[Class]]` var toString = Object.prototype.toString; // 用於解析函式的反編譯程式碼 var fnToString = Function.prototype.toString; // 用於檢測宿主構造器 (Safari > 4 ;真的輸出特定的陣列) var reHostCtor = /^[object .+?Constructor]$/; // 用一個標準的原生方法作為模板,編譯一個正規表示式。 // 我們選擇 'Object#toString' 因為它一般不會被汙染。 var reNative = RegExp('^' + // 將 'Object#toString' 強制轉為字串 String(toString) // 轉義所有指定的正規表示式字元 .replace(/[.*+?^${}()|[]/]/g, '$&') // 用 '.*?' 替換提及的 'toString' ,以保持模板的通用性。 // 將 'for ...' 之類的字元替換掉,以相容 Rhino 等環境,因為這些環境新增了額外的資訊,如方法引數數量。 .replace(/toString|(function).*?(?=()| for .+?(?=])/g, '$1.*?') + '$' ); function isNative(value) { var type = typeof value; return type == 'function' // 用 'Function#toString' (fnToString)繞過了值(value)本身的 'toString' 方法,以免被偽造所欺騙。 ? reNative.test(fnToString.call(value)) // 回退到宿主物件的檢查,因為某些環境(瀏覽器)將型別陣列(typed arrays)之類的東西當作 DOM 方法,此時可能不遵循標準的原生正規表示式。 : (value && type == 'object' && reHostCtor.test(toString.call(value))) || false; } // 匯出函式 module.exports = isNative; }()); // 用法 isNative(alert); // true isNative(myCustomFunction); // false |
這個函式雖不完美,但它能完成任務!
insertRule
我們都知道能通過選擇器(通過 document.querySelectorAll
)獲取一個 NodeList ,並可為每個元素設定樣式,但有什麼更高效的方法為選擇器設定樣式呢(例如你可以在樣式表裡完成):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var sheet = (function() { // 建立 <style> 標籤 var style = document.createElement('style'); // 如果你需要指定媒介型別,則可以在此新增一個 media (和/或 media query) // style.setAttribute('media', 'screen') // style.setAttribute('media', 'only screen and (max-width : 1024px)') // WebKit hack :( style.appendChild(document.createTextNode('')); // 將 <style> 元素新增到頁面 document.head.appendChild(style); return style.sheet; })(); // 用法 sheet.insertRule("header { float: left; opacity: 0.8; }", 1); |
這對於一個動態且重度依賴 AJAX 的網站來說是特別有用的。如果你為一個選擇器設定樣式,那麼你就不需要為每個匹配到的元素都設定樣式(現在或將來)。
matchesSelector
我們經常會在進行下一步操作前進行輸入校驗,以確保是一個可靠值,或確保表單資料是有效的,等等。但我們平時是怎麼確保一個元素是否有資格進行進一步操作呢?如果一個元素有給定匹配的選擇器,那麼你可以使用 matchesSelector
函式來校驗:
1 2 3 4 5 6 7 8 9 10 |
function matchesSelector(el, selector) { var p = Element.prototype; var f = p.matches || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || function(s) { return [].indexOf.call(document.querySelectorAll(s), this) !== -1; }; return f.call(el, selector); } // 用法 matchesSelector(document.getElementById('myDiv'), 'div.someSelector[some-attribute=true]') |
就這樣啦,上述 7 個 JavaScript 函式是每個開發者都應該時刻記著的。有哪個函式我錯過了呢?請把它分享出來!
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!
任選一種支付方式