• 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/..."
    

    或者使用 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)
    

    实现很简单,性能也很高,并且没有出现任何一个字面函数。

  • 相关阅读:
    jdbc连接Mysql数据库
    测试ibatis3连接数据
    dbcp参数配置
    努力---是永远且持久的行为
    android---textview控件学习笔记之显示表情图片和文本(二)
    android---textview控件学习笔记之显示文本(一)
    程序员的要求
    android的adb命令中,pm,am的使用
    完成celery简单发送注册邮件
    培养代码逻辑
  • 原文地址:https://www.cnblogs.com/index-html/p/js-functional-programming-example.html
Copyright © 2020-2023  润新知