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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 理解纯函数和非纯函数是向更清晰、更基于角色和可测试的代码的简单过渡。在这篇文章中,将通过查看一个简单的体重指数(BMI)计算器来探索纯函数和非纯函数,该计算器通过一些简单的身高和体重输入因素来估算“健康体重”。

理解纯函数和非纯函数是向更清晰、更基于角色和可测试的代码的简单过渡。在这篇文章中,将通过查看一个简单的体重指数(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 进行交互的函数就不是纯函数,因此在代码优化过程中,不要过份追求纯函数,尽可能的提纯函数即可。


相关文章
|
3月前
|
前端开发 JavaScript 开发者
揭秘JavaScript魔法三剑客:call、apply、bind,解锁函数新世界,你的前端之路因它们而精彩!
【8月更文挑战第23天】在 JavaScript 的世界里,`call`、`apply` 和 `bind` 这三个方法常常让新手感到困惑。它们都能改变函数执行时的上下文(即 `this` 的指向),但各有特点:`call` 接受一系列参数并直接调用函数;`apply` 则接收一个参数数组,在处理不确定数量的参数时特别有用;而 `bind` 不会立即执行函数,而是创建一个新版本的函数,其 `this` 上下文已被永久绑定。理解这三个方法能帮助开发者更好地运用函数式编程技巧,提升代码灵活性和可维护性。
37 0
|
29天前
|
前端开发 JavaScript
前端:实现一个 sleep 函数
在前端开发中,实现一个 `sleep` 函数可以用来暂停代码执行,模拟延迟效果,常用于测试或控制异步操作的节奏。该函数通常基于 `Promise` 和 `setTimeout` 实现,简单易用。
|
2月前
|
存储 前端开发 JavaScript
前端基础(十二)_函数高级、全局变量和局部变量、 预解析(变量提升)、函数返回值
本文介绍了JavaScript中作用域的概念,包括全局变量和局部变量的区别,预解析机制(变量提升),以及函数返回值的使用和类型。通过具体示例讲解了变量的作用域、函数的返回值、以及如何通过return关键字从函数中返回数据。
22 1
前端基础(十二)_函数高级、全局变量和局部变量、 预解析(变量提升)、函数返回值
|
2月前
|
存储 前端开发 JavaScript
前端基础(十一)_函数声明及调用、函数的形参与实参、arguments参数、函数的参数类型、函数中的问题
本文介绍了JavaScript中函数的声明及调用、形参与实参的概念、arguments对象的使用、函数参数的类型以及函数中this的作用。通过示例代码详细解释了函数如何接收参数、如何处理参数个数不匹配的情况,以及函数在不同上下文中this的指向。
22 1
|
3月前
|
JSON 前端开发 API
构建前端防腐策略问题之更新getMemoryUsagePercent函数以适应新的API返回格式的问题如何解决
构建前端防腐策略问题之更新getMemoryUsagePercent函数以适应新的API返回格式的问题如何解决
构建前端防腐策略问题之更新getMemoryUsagePercent函数以适应新的API返回格式的问题如何解决
|
2月前
|
JavaScript 前端开发
前端JS函数
【9月更文挑战第4天】前端JS函数
27 6
|
4月前
|
开发框架 前端开发 JavaScript
循序渐进VUE+Element 前端应用开发(22)--- 简化main.js处理代码,抽取过滤器、全局界面函数、组件注册等处理逻辑到不同的文件中
循序渐进VUE+Element 前端应用开发(22)--- 简化main.js处理代码,抽取过滤器、全局界面函数、组件注册等处理逻辑到不同的文件中
|
4月前
|
开发框架 JSON 前端开发
循序渐进VUE+Element 前端应用开发(7)--- 介绍一些常规的JS处理函数
循序渐进VUE+Element 前端应用开发(7)--- 介绍一些常规的JS处理函数
|
4月前
|
JavaScript 前端开发
前端框架原理自测题:根据 JSX / Vue 模板写出 render 函数 / VNode
前端框架原理自测题:根据 JSX / Vue 模板写出 render 函数 / VNode
27 0
|
4月前
|
前端开发 JavaScript
前端 JS 经典:箭头函数的意义
前端 JS 经典:箭头函数的意义
40 0