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

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

时间:2024-10-10 23:47:04浏览次数:8  
标签:function set console 函数 代码 编程 JS Ajax

文章目录

写在前面
不知道看到这里的朋友有没有真正消化 上篇 中介绍的知识,这些内容我在全网介绍 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 XMLHttpRequest
        ajax = new XMLHttpRequest();

    } else if (window.ActiveXObject) {
        // otherwise, use ActiveX for IE5 and IE6
        ajax = 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 XMLHttpRequest
    myAjax = function() {
      return new XMLHttpRequest();
    };
        
  } else if (window.ActiveXObject) {
    // it's ActiveX for IE5 and IE6
    myAjax = 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) 的相关概念,并将函数视为一等对象来使用。最后考察了一些在概念上满足纯粹的函数式编程的编码技术。这些知识将作为后续章节更高级技术的基石。让我们拭目以待!

标签:function,set,console,函数,代码,编程,JS,Ajax
From: https://blog.csdn.net/frgod/article/details/142834842

相关文章

  • 运行使用Electron-forge打包的electron package时遇到在js文件中执行的exec命令和在渲
    js文件中执行的exec命令出错很可能是项目中使用了一些非html,css,js的源文件,比如用了Makefile来编译了cpp代码,或者执行的exec命令为cpdir/something.cpp之类的文件操作命令。可以使用修改forge.config.js文件配置的方式,使得npmrunmake的时候自动把Makefile等exec命令中用到......
  • 基于Window网络编程课程设计(刘琰著)写tcp和udp双回射服务器思想及代码实现
    再写一遍双回射,主要还是按照书上走,也方便自己回顾理解而且这个代码完美解决了tcp阻塞问题,其实看懂这个代码也理解了为什么上篇的代码网络编程——实现tcp和udp的双回射服务器(c++)-CSDN博客会被阻塞,读者可以自己思考下本书还是采用的是select的方法来实现双回射的服务器。一......
  • 实验1 现代c++编程初体验
    实验1:1//现代C++标准库、算法库体验2//本例用到以下内容:3//1.字符串string,动态数组容器类vector、迭代器4//2.算法库:反转元素次序、旋转元素5//3.函数模板、const引用作为形参67#include<iostream>8#include<string>9#include......
  • Vavr - java函数式编程,分离业务代码与非业务代码神器
    微信公众号:阿俊的学习记录空间小红书:ArnoZhangwordpress:arnozhang1994博客园:arnozhangCSDN:ArnoZhang19941.入门指南使用Vavr的项目至少需要支持Java1.8。该.jar文件可以在MavenCentral获取。1.1.Gradledependencies{compile"io.vavr:vavr:0.10.4"}G......
  • 【编程小白必看】Python编程练习题元组操作秘籍一文全掌握
    【编程小白必看】Python编程练习题元组操作秘籍......
  • 实验1 现代C++编程初体验
    1.实验任务1task1源代码:1#include<iostream>2#include<string>3#include<vector>4#include<algorithm>56usingnamespacestd;78//声明9//模板函数声明10template<typenameT>//*****表明这是一个模板参数,可以接受任意类型的参数......
  • Java如何写一个构造函数
     构造函数是类的一个特殊成员函数,它在创建对象时被调用,用于初始化新创建的对象。在Java中,构造函数的名称必须与类名完全相同,没有返回类型(包括void)。构造函数可以有参数,也可以没有。Java中的构造函数示例假设我们想要创建一个Person类,包含name和age两个属性。我们可以这样定......
  • Java如何调用构造函数和方法以及使用
    调用构造函数的格式构造函数在创建新对象时被调用。调用格式如下:ClassNameobjectName=newClassName(parameters);ClassName:你需要创建其实例的类的名称。objectName:你将创建的对象的名称。parameters:如果你使用的是带有参数的构造函数,这里需要传递相应的参数。示例:......
  • 实验1 现代C++基础编程
    任务1:源代码task1.cpp1#include<iostream>2#include<string>3#include<vector>4#include<algorithm>56usingnamespacestd;78template<typenameT>9voidoutput(constT&c);1011voidtest1();12void......
  • jsp传统文化在线学习系统3wm23--程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表学生,教师,课程分类,课程信息,选择课程,退出课程,学习资源开题报告内容一、项目背景在全球化和信息化的背景下,传统文化面临着被边缘化的风险。为了保护和传承......