天天看點

JS 函數式程式設計案例

JS 函數式程式設計案例

前言

曾經遇到過的一個 JS 需求,檢測頁面是否加載過帶有某個特征的 URL。實作原本很簡單(假設特征為 xxx):

var entries = performance.getEntriesByType('resource')

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

但由于某些原因,代碼不能使用循環,不能使用字面函數(包括 function 、箭頭函數等),eval 之類的更不用提了。

也就是說,即使要用回調,也隻能通過 JS 或 DOM 内置 API 建立。

下面開始挑戰。

簡單但低效的方案

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

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

不過該方案存在性能問題。由于 Performance API 記錄了大量資訊,導緻序列化的開銷非常大,在一些内容很多的頁面非常明顯。

函數式程式設計

我們嘗試用 JS 或 DOM 内置 API 建立函數。回顧本文開頭的代碼:

var entries = performance.getEntriesByType('resource')

var existed = 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 existed = urls.some(function(v) {
  return /xxx/.test(v)
})
      

下面開始逐一突破。

屬性讀取函數化

若想通過函數調用的方式讀取屬性,顯然需要用到 讀通路器,即 getter。

Performance API 記錄中的 name 屬性定義于 PerformanceEntry 類,是以可通過如下方式擷取該屬性的 getter:

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

現在讀取屬性,即可抛棄 entry.name 的形式,換成函數調用的形式:

nameGetter.call(entry)   // "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/..."
      

完整實作

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)