Understanding pure and non pure functions is a simple transition to clearer, more role-based and testable code. In this article, we will explore pure and non pure functions by looking at a simple body mass index (BMI) calculator that estimates "healthy weight" through some simple height and weight input factors.
concept
Briefly described above What are pure and impure functions Here is a brief list:
-
Pure functions: pure functions are easier to understand, especially because the code base can be extended and role-based functions can do a job well. Pure functions do not modify external variables, states and data outside the scope, and return the same output given the same input. Such functions are considered "pure".
-
Impure function: a function that changes variables, states and data outside its scope, so it is regarded as "impure". There are many ways to write JavaScript. From the perspective of non pure / pure functions, you can write code that is easier to reason.
The following is a BMI calculator created in a completely impure way, and then refactored into multiple functions using pure functions.
HTML and events
This is a form created to capture user input data:
<form name="bmi"> <h1>BMI Calculator</h1> <label> <input type="text" name="weight" placeholder="weight (kg)" /> </label> <label> <input type="text" name="height" placeholder="height (cm)" /> </label> <button type="submit">Calculate immediately</button> <div class="calculation"> <div>BMI Value: <span class="result"></span></div> <div>Health index:<span class="health"></span></div> </div> </form>
Add an event listener for the button below:
<script> (() => { const elForm = document.querySelector("form[name=bmi]"); const onSubmit = (event) => { event.preventDefault(); }; elForm.addEventListener("submit", onSubmit, false); })(); </script>
Implementation of impure
You will now delete the contents of IIFE and event handlers and focus on the onSubmit function:
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 = "Lean weight"; } else if (bmi > 18.5 && bmi < 25) { healthMessage = "Healthy weight"; } else if (bmi > 25) { healthMessage = "Obesity weight"; } result.innerHTML = bmi; health.innerHTML = healthMessage; };
This is all that the onSubmit event function contains. Enter the height / weight, and it will update the DOM with these results. Now, this is where non pure functions are extremely difficult to debug and understand, especially for non front-end developers. Of course, in order to make the code easy to read, you can add some comments, as follows:
const onSubmit = (event) => { // Prevent form from actually submitting event.preventDefault(); let healthMessage; // Get the < span > tags of DOM result and health to inject the results const result = elForm.querySelector(".result"); const health = elForm.querySelector(".health"); // According to the weight and height values, it is parsed into an integer with base 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 = "Lean weight"; } else if (bmi > 18.5 && bmi < 25) { healthMessage = "Healthy weight"; } else if (bmi > 25) { healthMessage = "Obesity weight"; } // Insert the result into the corresponding DOM result.innerHTML = bmi; health.innerHTML = healthMessage; };
It seems easy to understand, and then it needs a lot of comments, which are often unable to describe. Let's reconstruct it as a pure function.
Implementation of pure function
Before using pure functions, too many things were done in the onSubmit function in the above impure implementation:
- Read value from DOM
- Parse values into numbers
- Calculate BMI from analytical value
- Conditionally check the BMI results and assign the correct message to the undefined variable healthMessage
- Write value to DOM
To refine the implementation, you will implement the functions that handle these operations:
- Parse values into numbers and calculate BMI
- Return the correct message bound to the DOM
Start purification
Start with the analysis and calculation of BMI of the input value, and focus on this Code:
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);
This involves the formula of parseInt() to calculate BMI. When refactoring or adding more functions at a certain time in the application, it is not very flexible and is likely to be error prone (the client input is unreliable).
To refactor, you only need to get the value attribute of each input separately and delegate them to a getBMI function:
const weight = elForm.querySelector("input[name=weight]").value; const height = elForm.querySelector("input[name=height]").value; const bmi = getBMI(weight, height);
The getBMI function will be 100% pure because it takes parameters and returns a new data based on these parameters. Given the same input, it will get the same output. The specific codes are as follows:
const getBMI = (weight, height) => { const newWeight = parseInt(weight, 10); const newHeight = parseInt(height, 10); return (newWeight / (((newHeight / 100) * newHeight) / 100)).toFixed(1); };
This function takes weight and height as parameters, converts them to the Number parseInt, and then performs the calculation of BMI. Whether you pass String or Number as each parameter, you can check security.
Go to the next function. The calculation logic of healthMessage is as follows:
health.innerHTML = getHealthMessage(bmi);
Similarly, start implementing the logic of getHealthMessage:
const getHealthMessage = (bmiValue) => { let healthMessage; if (bmiValue < 18.5) { healthMessage = "Lean weight"; } else if (bmiValue > 18.5 && bmiValue < 25) { healthMessage = "Healthy weight"; } else if (bmiValue > 25) { healthMessage = "Obesity weight"; } return healthMessage; };
The above function is also a 100% pure function with fixed input and output.
So far, the code has been refactored. The complete code is as follows:
<script> (() => { const elForm = document.querySelector("form[name=bmi]"); const getHealthMessage = (bmiValue) => { let healthMessage; if (bmiValue < 18.5) { healthMessage = "Lean weight"; } else if (bmiValue > 18.5 && bmiValue < 25) { healthMessage = "Healthy weight"; } else if (bmiValue > 25) { healthMessage = "Obesity weight"; } 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>
In front-end project development, it is difficult to ensure that all functions are pure functions, because as long as the functions that need to interact with DOM are not pure functions. Therefore, in the process of code optimization, do not excessively pursue pure functions and purify functions as much as possible.