1. 程式人生 > 其它 >JS 函數語言程式設計案例

JS 函數語言程式設計案例

JS 函數語言程式設計案例

前言

最近遇到一個應用場景,用於檢測頁面是否載入過帶有某個特徵的 URL。實現原本很簡單:

var entries = performance.getEntriesByType('resource')

var ret = entries.some(function(v) {
  return /xxx/.test(v.name)
})

但由於某些原因,要求程式碼中不能出現 function 關鍵字。(箭頭函式不用考慮,語法相容性就不行。至於 eval 之類的動態執行那就更不用提了)

也就是說,即使要有回撥,函式也只能通過 JS 或 DOM 內建的 API 建立。

下面開始挑戰。

簡單但低效的方案

最先想到的方案非常簡單,根本不用函式。直接將 entries 陣列序列化成字串,一步到位:

var str = JSON.stringify(entries)
var ret = /xxx/.test(str)

不過該方案存在效能問題。由於 Performance API 記錄了大量資訊,導致序列化的開銷非常大。

例如淘寶首頁,開啟後往下翻幾頁,URL 記錄多達數百條,序列化用時數毫秒,字串長度超過十萬。

函數語言程式設計

回到主題,我們嘗試用 JS 或 DOM 內建的 API 建立回撥函式。

回顧本文開頭的程式碼:

var entries = performance.getEntriesByType('resource')

var ret = entries.some(function(v) {
  return /xxx/.test(v.name)  // 如何用 JS 內建的方法實現這個邏輯?
})

回撥函式中的邏輯看似簡單,但實際上做了兩件事:讀取 name 屬性、呼叫 test 方法。

為了方便理解,我們將這兩件事進行拆分,每次只做一件。

1.讀取屬性

我們將 entries 陣列轉換成 urls 字串陣列,類似如下邏輯:

var urls = entries.map(function(v) {
  return v.name
})

2.呼叫方法

urls 陣列中搜索關鍵字,類似如下邏輯:

var ret = urls.some(function(v) {
  return /xxx/.test(v)
})

下面開始逐一突破。

屬性讀取函式化

若想通過函式呼叫的方式讀取屬性,顯然需要用到 讀訪問器

,即 getter

Performance API 記錄中的 name 屬性定義於 PerformanceEntry 類,因此可通過如下方式獲取該屬性的 getter

var nameGetter = Object.getOwnPropertyDescriptor(PerformanceEntry.prototype, 'name').get

現在讀取屬性,即可拋棄 entries[n].name 的形式,換成函式呼叫的形式:

nameGetter.call(entries[n])   // "https://..."

是不是有種倒裝句的感覺?

我們把謂語放在最前,主語放在最後。因為我們強調的是讀屬性這個行為,而不是強調讀誰的。這個「誰」,可以指代陣列中任何一個元素。

現在,我們程式碼變成了這樣:

// 臨時版
var urls = entries.map(function(v) {
  return nameGetter.call(v)
})

顯然,如果能直接將 nameGetter.call 傳給 map 回撥,那麼 function 就可以去掉了。

// 這樣可以嗎?好像缺了什麼。。。
var urls = entries.map(nameGetter.call)

因為 nameGetter.call 只是 Function.prototype.call 的一個引用,是不帶上下文的。

nameGetter.call === Function.prototype.call // true

好在陣列的 map 方法還有 第二個引數,用於設定回撥函式的 this 上下文。

於是,我們可以把 nameGetter 作為 map 的第二個引數:

// 大功告成
var urls = entries.map(nameGetter.call, nameGetter)

成功得到所有記錄的 URL 陣列!

呼叫方法

事實上,陣列的迭代方法都支援設定 this 上下文。

因此,我們使用同樣的思路,實現字串陣列的正則搜尋。例如:

var reg = /google/

urls.find(reg.test, reg)  // "https://www.google.com/..."

或者使用 some 方法,直接判斷是否存在。

完整實現

var nameGetter = Object.getOwnPropertyDescriptor(PerformanceEntry.prototype, 'name').get

var entries = performance.getEntriesByType('resource')

var urls = entries.map(nameGetter.call, nameGetter)

var reg = /xxx/

urls.some(reg.test, reg)

實現很簡單,效能也很高,並且沒有出現任何一個字面函式。