【玩转 JS 函数式编程_011】3.2 JS 函数式编程筑基之:以函数式编程的方式活用函数(下)+ 3.3 本章小结

news/2024/10/22 6:16:43/

文章目录

    • 3.2.4. 填充脚本 Polyfills
      • 1. 检测 Ajax(Detecting Ajax)
      • 2. 替代函数 Adding missing functions
    • 3.2.5. 插入处理 Stubbing
    • 3.2.6. 即时调用 Immediate invocation
  • 3.3. 本章小结 Summary

写在前面
不知道看到这里的朋友有没有真正消化 上篇 中介绍的知识,这些内容我在全网介绍 JS 函数式编程的资料里还没见过类似的版本。希望大家继续保持空杯心态,持续深耕,学通悟透 JS 函数式编程就是水到渠成的事了。一起来看 FP 筑基部分的下篇——

3.2.4. 填充脚本 Polyfills

就像为普通变量赋值那样,能够对函数动态赋值的做法,还能在定义 polyfill 时更高效。

1. 检测 Ajax(Detecting Ajax)

回到 Ajax 刚开始出现的年代,鉴于不同的浏览器以不同的方式实现了 Ajax 调用,编写代码时必须考虑这些实现差异。 以下代码演示了 Ajax 调用的特性检测逻辑:

function getAjax() {let ajax = null;if (window.XMLHttpRequest) {// modern browser? use XMLHttpRequestajax = new XMLHttpRequest();} else if (window.ActiveXObject) {// otherwise, use ActiveX for IE5 and IE6ajax = new ActiveXObject("Microsoft.XMLHTTP");} else {throw new Error("No Ajax support!");}return ajax;
}

这段代码是行得通的,但也意味着每次调用都要进行 Ajax 检测,即便测试结果永远不变。其实利用函数作一等对象,可以得到一种更有效的实现方式:定义两个不同的函数,其中一个完成特性检测且只执行一次,然后将正确的实现逻辑赋给另一个函数,以供后续调用。代码如下:

(function initializeGetAjax() {let myAjax = null;if (window.XMLHttpRequest) {// modern browsers? use XMLHttpRequestmyAjax = function() {return new XMLHttpRequest();};} else if (window.ActiveXObject) {// it's ActiveX for IE5 and IE6myAjax = function() {new ActiveXObject("Microsoft.XMLHTTP");};} else {myAjax = function() {throw new Error("No Ajax support!");};}window.getAjax = myAjax;
})();

这段代码演示了两个重要概念:

  1. 可以对函数动态赋值:运行代码,window.getAjax(即全局 getAjax 变量)会根据当前浏览器获取到三个候选项之一。稍后调用 getAjax() 时,将执行正确的函数,无需再进行任何浏览器特性检测;
  2. 定义了 initializeGetAjax 函数并立即执行——该模式称为 立即调用函数表达式(IIFE)。函数按预期运行并在完成逻辑后自行清理内存,因为它的所有变量都是局部变量,甚至在函数运行结束后就不存在了。

2. 替代函数 Adding missing functions

这种在运行时定义函数的想法也可用于编写替代脚本 polyfills 来提供其他缺失的函数。例如,所需代码逻辑如下:

if (currentName.indexOf("Mr.") !== -1) {// it's a man...
}

可能您更喜欢下面这样的新版实现:

if (currentName.includes("Mr.")) {// it's a man...
}

假设浏览器不支持 .includes() 怎么办呢?同样,我们可以按需定义想要的函数:若 .includes() 可用,则什么都不动;否则定义一个 polyfill 来实现同样的逻辑。代码如下:

小贴士

Mozilla 开发者网站为新版 JavaScript 特性提供了大量填充脚本。例如,可以直接从 官方文档 获取到 includes 的实现。

if (!String.prototype.includes) {String.prototype.includes = function(search, start) {"use strict";if (typeof start !== "number") {start = 0;}if (start + search.length > this.length) {return false;} else {return this.indexOf(search, start) !== -1;}};
}

这段代码在运行时它会检查 String 原型是否已实现 include 方法。若没有,则会为其定义一个执行相同逻辑的函数;此后便都能使用 .includes() 了。顺便提一下,还有其他定义 polyfill 的方法,详见 思考题 3.5。

提示

直接修改标准类型的原型对象通常是不被接受的,因为本质上相当于使用了全局变量,容易出错;然而,为一个成熟且已知的函数编写一个 polyfill 这种情况是不大可能引发什么代码冲突的。

最后,如果您碰巧认为前面演示的 Ajax 示例已经过时,那么请考虑一下这个场景:若要使用更现代的 fetch() 调用服务,您还会发现并非所有现代浏览器都支持该 API(查看 http://caniuse.com/#search=fetch 验证),因此您必须使用 polyfill,例如 https://github.com/github/fetch 上的那版。研究上面的源码,会发现它基本上也用到了与前面相同的方法:检测是否需要 polyfill,需要则创建一个。

3.2.5. 插入处理 Stubbing

本节来看一个与 polyfill 类似的应用场景:让函数根据环境完成不同的逻辑。这里的要领在于执行“打桩”——这个源于测试的概念要求将一个函数替换为另一个更简单的函数逻辑,而不走原逻辑。

“打桩”通常与日志一起使用。例如,需要应用程序在开发阶段执行详细的日志记录,而非在生产环境执行。一个常见的解决方案如下:

let myLog = someText => {if (DEVELOPMENT) {console.log(someText); // or some other way of logging} else {// do nothing}
}

这段代码没问题,只是与前面的 Ajax 调用示例一样,代码完成的逻辑比预想的多,因为每次都会检查程序是否处于开发状态。 如果将日志函数存根,使其实际上不记入任何内容,则可简化代码并获得少量的性能提升:

let myLog;
if (DEVELOPMENT) {myLog = someText => console.log(someText);
} else {myLog = someText => {};
}

进一步使用三目运算符优化:

const myLog = DEVELOPMENT? someText => console.log(someText): someText => {};

尽管代码有点不好理解,但我更倾向于这种写法,因为用到了常量,它的值不可变更。

提示

鉴于 JavaScript 允许使用比参数声明更多的参数来调用函数,考虑到未处于开发状态时,myLog 函数不执行任何操作,因此也可以写为 () => {},这样也行得通;然而笔者更倾向于函数签名的统一,所以保留了 someText 参数,即便不参与运行。具体视个人喜好而定。

后续您会反复看到函数作一等对象使用的身影。查看所有的示例代码就知道了。

3.2.6. 即时调用 Immediate invocation

还有一种常见的函数用法,常常出现在流行的 JavaScript 库和框架中,可将其他语言的一些模块化特性引入到甚至是旧版 JavaScript 的项目内,代码如下:

(function() {// do something...
})();

专家提示

另一种等效写法为 (function(){ ... }()),注意两者的细微差别。两种风格都有各自的拥护者。选择自己喜欢的风格,并注意保持写法上的一致即可。

也可以将一些参数传入函数作为其初始值:

(function(a, b) {// do something, using the// received arguments for a and b...
})(some, values);

最后,也可以令函数返回某些值:

let x = (function(a, b) {// ...return an object or function
})(some, values);

前面提到,该模式称为 IIFE(读作 /iffy/),名字也不难理解:定义一个函数并立即调用该函数。至于为什么要这么写,而不是简单地编写内联代码,就要考虑作用域的问题了。

拓展

留意函数周围的括号。这能帮助解析器理解此时正在编写一个表达式,如若省略,JavaScript 就会认定这是一个函数声明而非函数调用。小括号也能用作视觉注释(visual note),以便读到这段代码的人立即认出 IIFE 写法。

由于 JavaScript 定义的作用域是函数级作用域,在 IIFE 中定义的变量或函数都将是内部变量,外面任何位置的代码都无法访问。假如要实现一些复杂的初始化逻辑,例如:

function ready() { ... }
function set() { ... }
function go() { ... }// initialize things calling ready(),
// set() and go() appropriately

可能出现的问题,在于如果出现同名的函数,则变量提升效应作用的结果,将导致实际执行的是最后声明的那个函数:

function ready() {console.log("ready");
}function set() {console.log("set");
}function go() {console.log("go");
}ready();
set();
go();function set() {console.log("UNEXPECTED...");
}
// "ready"
// "UNEXPECTED"
// "go"

出错了吧!如果改用 IIFE,问题就可以避免。这么一来,其余代码甚至都看不到那三个内部函数,从而有效避免了全局命名空间的污染,典型代码实现如下:

(function() {function ready() {console.log("ready");}function set() {console.log("set");}function go() {console.log("go");}ready();set();go();
})();function set() {console.log("UNEXPECTED...");
}
// "ready"
// "set"
// "go"

如果 IIFE 中包含 return 语句,则可以回顾第一章中的相关示例,然后用如下代码实现一个计数器:

const myCounter = (function() {let count = 0;return function() {count++;return count;};
})();

这之后,每调用一次 myCounter(),都会返回一个递增的计数值,但是代码的任何其他部分都不会覆盖到 IIFE 内部计数变量 count,因为它只能在返回的函数中被访问到。

3.3. 本章小结 Summary

本章讨论了 JavaScript 定义函数的几种方法,重点考察了箭头函数。它较标准函数具有更简洁的优点。同时还学习了 科里化(currying) 的相关概念,并将函数视为一等对象来使用。最后考察了一些在概念上满足纯粹的函数式编程的编码技术。这些知识将作为后续章节更高级技术的基石。让我们拭目以待!


http://www.ppmy.cn/news/1538970.html

相关文章

探索高效的 PDF 拆分工具及其独特功能

当一份大型的PDF文档包含了多个不同主题或章节的内容时,将其拆分成独立的部分可以更方便我们的阅读、编辑和管理。接下来,让我们一起走进PDF拆分工具的世界,了解它们的功能和价值。 1.福昕PDF编辑器 链接一下>>https://editor.foxits…

Keepalived LVS群集

keepalived群集 Keepalived是一个基于VRRP协议来实现的LVS服务高可用方案,可以解决静态路由出现的单点故障问题。 群集具备的特性 1)负载均衡 提高群集的性能 2)健康检查(探针) 探测调度器和节点服务器是否在正常运…

商标已拿证,为何无缘无故被撤销?

什么是“撤三”? 《商标法》第四十九条规定,“注册商标成为其核定使用的商品的通用名称或者没有正当理由连续三年不使用的,任何单位或者个人可以向商标局申请撤销该注册商标。”这就是我们通常所说的“撤三”。 那么怎样才算规范、正确的商标…

操作系统笔记---进程的同步与互斥方法汇总

实现互斥的四个原则主要特点空闲让进忙则等待有限等待让权等待软件方法单标志法❌turn 1/0双标志先检查法❌flag[i] true/flase双标志后检查法❌❌flag[i] true/flasePerterson法❌turn 1/0 flag[i] true/flase硬件方法关中断不适用多处理机 关中断权…

Sealos Devbox 发布,珍爱生命,远离 CI/CD

水滴攻击太阳系用的是最原始的攻击方式:撞击!却又如此有效率。 当我们搞了一堆容器、编排、CI/CD、DevOps,发明了一大堆没什么用的名词之后,最终发现这些操作都是花里胡哨,让开发者越陷越深。 最终你会发现一个真理&…

【大数据应用开发】2023年全国职业院校技能大赛赛题第09套

如有需要备赛资料和远程培训,可私博主,详细了解 目录 任务A:大数据平台搭建(容器环境)(15分) 任务B:离线数据处理(25分) 任务C:数据挖掘(10分) 任务D:数据采集与实时计算(20分) 任务E:数据可视化(15分) 任务F:综合分析(10分) 任务A:大数据平台搭…

MySQL 的数据类型

1.整数类型 1.1 tinyint tinyint 为小整数类型,存储空间为1个字节(8位),有符号范围-128 ~ 127,无符号范围 0 ~ 255,此类型通常在数据库中表示类型的字段,如某一字段 type 表示学科,其中 “type1” 表示语文…

【常用的安装破解版指令】MAC安装破解版软件显示文件损坏时

MAC安装破解版软件显示文件损坏时 复制以下命令粘贴到终端后 sudo xattr -rd com.apple.quarantine 打开Finder(访达),点击左侧的 应用程序,将应用拖进终端中,然后按键盘的回车键(return)&…