Rethinking the foundations: An in-depth view of Javascript functions.
Tomas Ezequiel Gil Amoedo
Front-End Developer | JavaScript | React & Vue | Nextjs
First of all, the foundations.
You could say it's a paradox to be writing about functions in 2024, with the AI hype and all this intricate plethora of never ending new topics (beautiful word, isn't it?).
The thing is, I honestly believe most people, including myself, before diving deep into this topic, do not truly grasp the power of functions, we tend to overlook more "complex" aspects like scoping, closures (which might be one of the most powerful topics in JS), and I'm not even going to mention technical communication.
What's a parameter and what's an argument?
Well, the point of this article is not to give you a boilerplate of functions to use in React/Next or Vue.js. The goal is to give you clarity.
We'll start with basic implementations, then see how and why to generalize them, we'll explore scope, touch on technical communication, and peek into declarative programming. To wrap up, we'll refactor a function three ways and I'll point you to some challenges to solidify these concepts.
The Simplest of Beginnings
Take a look at these two functions:
function eightSquare(num) { return 8 * 8}
function nineSquare(num) { return 9 * 9}
// keep going...
what principle am I breaking here? ?? DRY (Don’t Repeat Yourself)
So What we do? We generalize the function but we leave a little bit of that function TBD: to be determined/ to be defined.
function squareNum(num) {return num * num}
squareNum(2) // 4
squareNum(4) // 16
squareNum(6) // 36
Here's something really interesting: the more "to be defined" aspects you leave in a function, the more dynamically scoped it becomes.
While JavaScript is a lexically scoped language, meaning a variable's accessibility is determined by where the function is written, parameters add a twist.
By leaving these placeholders to be filled later, you're creating a bridge between lexical and dynamic scoping. It's as if you're saying:
"I'll decide what goes here when I use the function, not when I write it."
This interplay between fixed structure and flexibility is what makes functions in JavaScript so powerful.
Each parameter you add is like pushing the boundaries of lexical scope one step further, opening new possibilities for how your code can behave.
function squareNum(num) { return num * num; }
This behavior we just saw raises a natural question… what if we can abstract/generalize the functionality also?
What if I say, I want to decide the instructions later, and change them every time I use them?
That’s what Higher Order Functions are for.
From Repetition to Abstraction: Introducing Higher Order Functions.
Let's look at two similar functions:
function copyArrayAndDivideBy2(arr) {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
newArr.push(arr[i] / 2);
}
return newArr;
}
function copyArrayAndMultiplyBy2(arr) {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
newArr.push(arr[i] * 2);
}
return newArr;
}
what principle am I breaking here? ?? DRY (Don’t Repeat Yourself)
领英推荐
So What we do? We generalize:
function copyArrayAndManipulate(arr, instructions) {
let newArr = [];
for (let i = 0; i < arr.length; i++) {
newArr.push(instructions(arr[i]));
}
return newArr;
}
But how is this possible?
In JavaScript, functions are first-class objects. They can be:
This is the key to Higher Order Functions (HOFs).
If you've been paying attention, you might realize that "copyArrayAndManipulate" is essentially the .map() method for arrays.
Now, what's the takeaway from this? There are more than one, but I would argue that seeing .map() under the hood will give you more clarity than you think. I also believe that now, you can probably code your own .reduce() method.
Below, I’ve shared a function called "copyArrayAndManipulateWithAccumulator", which mimics a simple .reduce().
Before checking it out, I challenge you to try coding your own version. I'm more than convinced that now you have all the necessary tools to write it, if you can't don't stress about it, you will see it's actually pretty simple.
Understanding that functions like .map() or .reduce() aren't magical but structured loops with callbacks puts you in control.
The next time someone praises these array methods, you’ll know what’s happening under the hood, and you'll have the ability to write your own versions.
This isn’t just about being a better coder; it’s about mastering your tools. So, next time you use .map(), .filter(), or .forEach(), you’ll truly understand what’s happening, and that's the difference between guessing and knowing.
If you’ve already coded your own reduce method, congratulations! Here's a super simplistic mimic of .reduce() for you to compare:
function copyArrayAndManipulateWithAccumulator(arr, callback, initialValue) {
let accumulator = initialValue;
for (let i = 0; i < arr.length; i++) {
accumulator = callback(accumulator, arr[i]);
}
return accumulator;
}
Lastly, JavaScript has been evolving as a more Declarative Language over the years. So, if you can solve a problem imperatively, force yourself to take it to the next level and try a declarative way solution, trust me, it will pay off.
Not only your understanding of the underlying logic will be stronger, your code will be:
Remember: Code is for humans. your code should be easily understood by other developers, actually that "other developer" I mentioned, he's you, couple of days after writing the code, so you'd better be clear when you declare your intent...
Adopt this motto and try to implement it: Declarative paradigm is about declaring your intent. WHAT do I want to do? I declare my intention and leave the language to handle the abstractions.
Let's set a last example before wrapping up:
// Imperative solution:
function sumAll(arr) {
let total = 0;
for (let i = 0; i < arr.length; i++) {
total += arr[i];
}
return total;
}
// Declarative solution:
function sumAll(arr) {
let total = 0;
arr.forEach((num) => (total += num));
return total;
}
// Declarative solution using reduce:
function sumAll(arr) {
return arr.reduce((acc, curr) => acc + curr);
}
Notice how each solution becomes more concise, more legible, and focuses more on WHAT we want to achieve rather than HOW to achieve it. That's the power of declarative programming and higher-order functions in action.
For those of you looking to challenge yourselves:
I've put together a repository with 10 problems to help you practice these concepts. As an example, I've solved Challenge 9 (one of my favorites) using three different approaches:
Feel free to code and test your own solutions. You'll find all the instructions in this Repository.
This exercise isn't just about solving problems. It's about exploring different ways to approach them and deepening your understanding of JavaScript functions.
Senior Frontend Engineer | Typescript | Next.js | React.js
2 周So good brother ??, learned a lot.
Ex-Insight Fullstack Developer | MERN, Next.js, Tailwind, Selenium | Creator of Tabber (500+ Downloads) | Open to New Opportunities
1 个月Great insight about scopes and HOCs!