2

前端开发中使用纯函数提纯非纯函数

 2 years ago
source link: https://my.oschina.net/devpoint/blog/5297939
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前端开发中使用纯函数提纯非纯函数

理解纯函数和非纯函数是向更清晰、更基于角色和可测试的代码的简单过渡。在这篇文章中,将通过查看一个简单的体重指数(BMI)计算器来探索纯函数和非纯函数,该计算器通过一些简单的身高和体重输入因素来估算“健康体重”。

上文简单介绍了什么是纯函数和非纯函数,这里在简单列举一下:

  • 纯函数:纯函数更容易理解,特别是因为代码库可以扩展,以及基于角色的函数可以完成一项工作并且做得很好。纯函数不会修改作用域之外的外部变量、状态、数据,并且在给定相同输入的情况下返回相同的输出,这样的函数就被认为是“纯”的。

  • 非纯函数:在其作用域范围之外改变变量、状态、数据的函数,因此因此将其视为“不纯”。编写 JavaScript 的方法有很多种,从非纯/纯函数的角度考虑,可以编写更容易推理的代码。

下面以完全不纯的方式创建的 BMI 计算器,然后再将其重构为多个使用纯函数的函数。

HTML和事件

这是创建的用于捕获用户输入数据的表单:

<form name="bmi">
    <h1>BMI计算器</h1>
    <label>
        <input type="text" name="weight" placeholder="体重 (kg)" />
    </label>
    <label>
        <input type="text" name="height" placeholder="身高 (cm)" />
    </label>
    <button type="submit">立即计算</button>
    <div class="calculation">
        <div>BMI值: <span class="result"></span></div>
        <div>健康指数:<span class="health"></span></div>
    </div>
</form>

下面为按钮增加事件监听器:

<script>
    (() => {
        const elForm = document.querySelector("form[name=bmi]");
        const onSubmit = (event) => {
            event.preventDefault();
        };

        elForm.addEventListener("submit", onSubmit, false);
    })();
</script>

非纯的实现

现在将删除 IIFE 和事件处理程序的内容,并专注于 onSubmit 函数:

const onSubmit = (event) => {
    event.preventDefault();

    let healthMessage;
    const result = elForm.querySelector(".result");
    const health = elForm.querySelector(".health");
    const weight = parseInt(
        elForm.querySelector("input[name=weight]").value,
        10
    );
    const height = parseInt(
        elForm.querySelector("input[name=height]").value,
        10
    );
    const bmi = (weight / (((height / 100) * height) / 100)).toFixed(1);
    if (bmi < 18.5) {
        healthMessage = "偏瘦体重";
    } else if (bmi > 18.5 && bmi < 25) {
        healthMessage = "健康体重";
    } else if (bmi > 25) {
        healthMessage = "肥胖体重";
    }

    result.innerHTML = bmi;
    health.innerHTML = healthMessage;
};

这就是 onSubmit 事件函数包含的所有内容,输入身高/体重,它就会用这些结果更新 DOM。现在,这就是非纯函数极难调试和理解的地方,特别是对于非前端开发人员来。当然为了代码的易读性,可以将其加些注释,如下:

const onSubmit = (event) => {
    // 阻止表单实际提交
    event.preventDefault();

    let healthMessage;
    // 获取DOM result 和 health 的 <span> 标签以将结果注入
    const result = elForm.querySelector(".result");
    const health = elForm.querySelector(".health");

    // 根据重量和身高值解析为基数为 10 的整数
    const weight = parseInt(
        elForm.querySelector("input[name=weight]").value,
        10
    );
    const height = parseInt(
        elForm.querySelector("input[name=height]").value,
        10
    );
    const bmi = (weight / (((height / 100) * height) / 100)).toFixed(1);
    if (bmi < 18.5) {
        healthMessage = "偏瘦体重";
    } else if (bmi > 18.5 && bmi < 25) {
        healthMessage = "健康体重";
    } else if (bmi > 25) {
        healthMessage = "肥胖体重";
    }
    // 将结果插入到相应的 DOM
    result.innerHTML = bmi;
    health.innerHTML = healthMessage;
};

看上去好像容易理解,然后需要大量的注释,很多时候都无法如何去描述。下面就以纯函数的方式对其重构。

纯函数的实现

在开始使用纯函数之前,在上面不纯的实现中,onSubmit 函数中做了太多的事情:

  • 从 DOM 中读取值
  • 将值解析为数字
  • 从解析值计算BMI
  • 有条件地检查 BMI 结果并将正确的消息分配给未定义的变量 healthMessage
  • 将值写入 DOM

为了提纯实现,将要实现处理这些操作的函数:

  • 将值解析为数字并计算 BMI
  • 将绑定到 DOM 的正确消息返回

先从输入值解析计算BMI开始,专门针对这一段代码:

const weight = parseInt(elForm.querySelector("input[name=weight]").value, 10);
const height = parseInt(elForm.querySelector("input[name=height]").value, 10);

const bmi = (weight / (((height / 100) * height) / 100)).toFixed(1);

这涉及 parseInt() 计算BMI的公式,当在应用程序中的某个时刻重构或添加更多功能时,这不是很灵活并且很可能很容易出错(客户端输入是不可靠的)。

为了重构,只需要单独获取每个输入的 value 属性,并将它们委托给一个 getBMI 函数:

const weight = elForm.querySelector("input[name=weight]").value;
const height = elForm.querySelector("input[name=height]").value;

const bmi = getBMI(weight, height);

这个 getBMI 函数将是 100% 纯的,因为它接受参数并根据这些参数返回一个新的数据,给定相同的输入,将获得相同的输出。具体代码如下:

const getBMI = (weight, height) => {
    const newWeight = parseInt(weight, 10);
    const newHeight = parseInt(height, 10);
    return (newWeight / (((newHeight / 100) * newHeight) / 100)).toFixed(1);
};

此函数将 weightheight 作为参数,将它们转换为数字 parseInt ,然后执行 BMI 的计算。无论是将 String 还是 Number 作为每个参数传递,都可以安全检查。

进入下一个函数。healthMessage 的计算逻辑,如下:

health.innerHTML = getHealthMessage(bmi);

同样,开始实现 getHealthMessage 的逻辑:

const getHealthMessage = (bmiValue) => {
    let healthMessage;
    if (bmiValue < 18.5) {
        healthMessage = "偏瘦体重";
    } else if (bmiValue > 18.5 && bmiValue < 25) {
        healthMessage = "健康体重";
    } else if (bmiValue > 25) {
        healthMessage = "肥胖体重";
    }
    return healthMessage;
};

上面的函数也是 100% 的纯函数,输入输出固定。

至此,代码就完成了重构,完整代码如下:

<script>
    (() => {
        const elForm = document.querySelector("form[name=bmi]");

        const getHealthMessage = (bmiValue) => {
            let healthMessage;
            if (bmiValue < 18.5) {
                healthMessage = "偏瘦体重";
            } else if (bmiValue > 18.5 && bmiValue < 25) {
                healthMessage = "健康体重";
            } else if (bmiValue > 25) {
                healthMessage = "肥胖体重";
            }
            return healthMessage;
        };

        const getBMI = (weight, height) => {
            let newWeight = parseInt(weight, 10);
            let newHeight = parseInt(height, 10);
            return (
                newWeight /
                (((newHeight / 100) * newHeight) / 100)
            ).toFixed(1);
        };

        const onSubmit = (event) => {
            event.preventDefault();

            const result = elForm.querySelector(".result");
            const health = elForm.querySelector(".health");

            const weight = elForm.querySelector("input[name=weight]").value;
            const height = elForm.querySelector("input[name=height]").value;

            const bmi = getBMI(weight, height);

            result.innerHTML = bmi;
            health.innerHTML = getHealthMessage(bmi);
        };

        elForm.addEventListener("submit", onSubmit, false);
    })();
</script>

在前端项目开发中很难保证所有函数都是纯函数,因为只要需要和 DOM 进行交互的函数就不是纯函数,因此在代码优化过程中,不要过份追求纯函数,尽可能的提纯函数即可。


Recommend

  • 76
    • www.ferecord.com 6 years ago
    • Cache

    撸js基础之函数 | 前端记录

    前端这两年的新技术铺天盖地,各种框架、工具层出不穷眼花缭乱。最近打算好好复习下 js 基础,夯实的基础才是学习新技术的基石。本文作为读书笔记简单的总结下 js 函数的基础知识。 本系列目前已有四篇: 各位有兴趣的可以看下。 本文日后还会...

  • 25
    • 掘金 juejin.im 6 years ago
    • Cache

    有意思的前端函数面试题

    1:考引用类型在比较运算符时候 隐式转换会调用本类型那个方法 toString和valueOf?(去年过年吵的很火国外的题) if(a == 1 &amp;&amp; a == 2 &amp;&amp; a == 3){ console.log("我

  • 58

    if(a == 1 &amp;&amp; a == 2 &amp;&amp; a == 3){ console.log("我走进来了"); } &lt;!--答案1:--&gt; var a = {num:0}; a.valueOf = functi

  • 51

    二战期间,青霉素曾拯救了无数人的生命,这都要得益于冻干技术的发明。什么?你居然不知道冻干技术是什么? 我们喝的速溶咖啡、吃的意大利面都是通过冻干技术制成的。对此,曾...

  • 32
    • 掘金 juejin.im 5 years ago
    • Cache

    前端之函数柯里化Currying

    什么是柯里化 在计算机科学中,柯里化(Currying)是一种技术(技巧),能够把本来接受 n 个参数的函数A,转换成只接收一个参数的函数B(B中的唯一参数,就是A的多个参数中的 第一个 参数)。 然后新函数B返回的,还是一个函数,记为C(注意原A中返回的不一

  • 30
    • 掘金 juejin.im 5 years ago
    • Cache

    前端工具函数

    将一级的数据结构处理成树状数据结构 处理成树状结构,一般就是需要节点和父节点标识,或者需要考虑以哪个节点为根节点生成树结构数据 // 使用示例代码: list: [{id: 1, pid: 0, name: 11}, {id: 2, pid: 1, nam

  • 5

    【前端工具】前端工具函数合集收集一些前端工具函数,整理成集,以便查询使用。随时更新:2021-03-03 正则匹配相关手机号正则验证匹配所有手机卡^(?:\+?86)?1(?:3\d{3}|5[^4\D]\d{2}|8\d{3}|7(?:[235-8]\d...

  • 3

    写该系列文章的初衷是“让每位前端工程师掌握高频知识点,为工作助力”。这是前端百题斩的第16斩,希望朋友们关注公众号“执鸢者”,用知识武装自己的头脑。16.1 基础每个对象都包含一个原型属性(prototype...

  • 13

    悟透前端:加深 Javascript 变量函数声明提升理解 Javascript变量函数声明提升(Hoisting...

  • 3

    欧雷说:「突然觉得,非纯 getter 类的 API 都可以考虑设计成流式接口(fluent interfac...」 欧雷 发表于 2022-12-02 12:29 ...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK