Gravitee Environment Variable Configuration for Docker
May 6, 2020CSS Animations
June 5, 2020Data Types And Complex Logic
Some of the most common logical sequences we write in JavaScript address management of data stored in Object, Array, or a mixed composition of the two data types. This forces us as developers to pick how we want to arrange, format, shape, or find what we need from these structures based on our relevant context. JavaScript provides a lot of tools in this area, particularly with Arrays – enough so, that it can become daunting to the developer to select the most appropriate tool in changing contexts. Option fatigue, becoming weary of selecting from a vast group of options, develops patterns in the mind of the developer. We find ourselves reaching for the few options we’ve come to trust to accomplish our goals. This leads to working, yet also far from efficient code at times.
In this post, I’m going to discuss some common iterative tools for working with Objects and Arrays in JavaScript. Learning these tools has helped me become more efficient and proficient at developing with the language.
Google defines iterative as “relating to or involving iteration, especially of a mathematical or computational process”. In Javascript, both Objects and Arrays can be used as iterative data sets. This means that we will iterate over either data type in various fashions to perform meaningful tasks.
In The Beginning: The For Loop
Traditionally, iterative work was done using a generic For Loop (link):
for(let i; i < 42; i++) {
//do work here until i increments to 42, then break
}
Even recently, I’ve seen this style of For Loop applied to a variety of data types. It’s standard, traditional, and stable. It’s also clumsy and wordy – managing a lot of things simultaneously to perform seemingly standard operations.
Improvements in the JavaScript language provided us with a few improvements on the generic For Loop.
For…In Loop
This loop uses the keyword “in” to denote the iterative property it is performing. It auto-completes when it reaches the end of its available iterative keys. Alternatively, a break statement will abort the loop on command.
for(let i in Object) {
//i is the key
}
For the Object data type, the iterative property “i” is the key.
for(let i in Array) {
//i is the index
}
For the Array data type, the iterative property “i” is the index of the Array instance.
The auto-assignment and default mapping of the For…In loop is quite helpful. We know that we will iterate dependably over every key/index exactly once and in order. This again is standard and stable. I have recently seen code that liberally applies this style of For…In looping for Objects and Arrays without prejudice, because it is something that works dependably. It can still be wordy, particularly with Arrays, and it forces us to consider our data type context manually to understand what we are iterating over.
For…Of Loop
This loop uses the keyword “of” to denote the iterative property it is performing. It auto-completes when it reaches the end of its available iterative keys. A break statement will abort the loop on command, like its brother For Loops. This type of loop does not work on Objects, but it is used for Arrays and other iterable JavaScript data like Maps, Sets, and Strings.
for(let i of Array) {
//i is the value of the iteration
}
With Arrays, this style of loop is commonly used to manage values within it as you can adjust an aspect of that value right within the loop. Typically when I see a For…Of Loop, the context is an Array having its values analyzed or adjusted.
All For Loops are standard, stable, and dependable. They also have the following drawbacks:
- They are wordy
- They create a unique memory context to operate with (a minor code optimization point)
- They are manual to make and handle, not native to the data types we are operating on
Regarding the third point, a lot of work was done particularly with the onset of ECMAScript 6 and beyond to enhance the methods of the Array and Object data types natively. This means that any data in the Array or Object data type has their respective methods natively available to it in real-time. Instead of manually creating a memory context to unfold, they are ready-to-go aspects of the language, able to be used right out of the box.
Array Methods
Array methods are a method on the Array data type with an encased callback that has access to the value, index, and the complete array itself. Most Array methods return a shallow copy of the original array, a feature highly prized by development systems that favor idempotence like functional programming.
Here are some of the common Array methods used today:
Array.forEach()
This is typically the first one that the developer encounters (if not map). It’s approachable because it mirrors the purpose of the for loops, giving us access to both the value and the index of what we are working with.
Array.forEach((value, index, array) => {
//logic done here
//return value; -> always return value
});
The Array.forEach() method is typically the least used and the option most frequently abandoned for various reasons, including a slight instability in its iterative properties. It’s typically overshadowed by the very similar…
Array.map()
Map is designed as the functional practice of iterating progressively over a data set with the option of affecting each value with an algorithm. This sounds pretty much like a for loop with the following exception: the map is designed to return a shallow copy of the original Array, the new one having the alterations, the original unscathed by the map operation.
Array.map((value, index, array) => {
//logic done here
//resulting value; -> always return the value
});
Map is used all over the place and usually in conjunction with some of the following methods.
Array.find()
Find is what you’re looking for when you’re looking for something in an Array. That’s its purpose.
Array.find((value, index, array) => {
//if condition is met, return the value, otherwise do nothing (no-op)
});
The result of an Array.find() is either the found data (based on your logic) or the special value: undefined. This is handy when utilized alongside Boolean logic (if statements). Array.find() will short circuit (end early) once it returns an instance from the callback, an efficiency perk.
Array.filter()
In other languages I’ve worked with, there’s a thing called “findAll”. Not so in JavaScript. In JavaScript, we have Array.filter().
Array.filter((value, index, array) => {
//return value or Boolean -> if it meets your condition
});
This returns a shallow copy of the original Array containing all of the elements that met your logic. If nothing matches your filter logic, you get an empty Array back. This is less useful in Boolean logic scenarios and more typically used to pair down a body of data to target data to either return or perform additional logic on.
Array.every()
Not nearly as well known as the others, Array.every() is used as a determinant on a complete set of data. If you want to know if something is true/false of an entire data set within an Array, this is what you reach for.
Array.every((value, index, array) => {
//return based on relation to condition
});
Array.every() return Boolean (true/false), not an Array. It’s a pure determinant. Much like its brother…
Array.some()
It took me a long time to use this particular method. It’s similar to Array.every() except that it only needs a single instance, like Array.find(), to return true. It also short circuits like Array.find(). Only if all values within the Array return false, does this method return false. Array.some() returns Boolean, not an Array, just like Array.every().
Array.some((value, index, array) => {
//return based on relation to condition
});
Array.reduce()
This is the granddaddy of all the Array methods. Many of the other Array methods are built upon it internally. It takes a much different format and is much more flexible than any of the others.
Array.reduce((aggregate, current, index, array) => {
//some logic between the aggregate and current values
//return the aggregate -> always return the aggregate
}, aggregate);
Array.reduce() is as much about the aggregate value it resolves as it is the Array we are reducing. It can relate to any data type in any way, based on the initialized aggregate (the third argument in the declarative structure). That set of options is what makes it a formidable companion. I have commonly used it to alter or construct Numbers, Strings, and Objects. I also use it to mesh two different Arrays in various ways.
Due to its pure power and the diverse options built into it, Array.reduce() is the last Array method typically adopted by a developer, but very quickly becomes a favorite tool once understood as it can simplify what would likely take much more code to accomplish with a combination of other Array methods.
Using Iterative Tools Together
Combinations of Array methods are very common because many of these methods are chainable – meaning they can be called in succession. This is possible for the ones that return shallow copies of Arrays such as map(), filter(), and reduce() (when it returns an Array). The basic rule is that if the data is Array data type, it continually has the Array methods available to it. Example:
Array.filter((value, index) => {
//return the ones what we want
}).map((value, index) => {
//change something on each value
}).reduce((agg, curr) => {
//do logic between the arrays
//return the state of the aggregate per iteration of the original Array
}, []);
The above example is overly generic, but clear and easy to read. We start with an array and
- filtered it to those values we wanted to
- perform a map operation on the Array values and then
- used those values to reduce against an empty array
All three operations were performed in sequence as a single declaration. This is a functionally styled example of what can be done with Arrays.
But I’m Using Objects…
The best iterative tools are the methods on the Array data type. But we commonly use Objects. A developer I recently built some nice tools with moved to a company where he joins, splices, and performs a variety of other actions on Object-formatted JSON files. So, for jobs like that, this question of what we do with Objects is extremely relevant.
Objects are open to the following options for iterative work:
- For…In Loop (described in the For Loop section above)
- Convert the Object to an Array format
Option 2, converting Object-to-Array is a real and increasingly relevant technique in JavaScript because of the power of the Array tools. Methods have been built into the Object data type itself to aid us in this manner. Here are a few of these:
- Object.keys(objectName) – this method creates an Array of all the top-level keys of the array (as strings)
- Object.values(objectName) – this method creates an Array of values from the top-level keys of the Object
- Object.entries(objectName) – this method creates an Array of Arrays, each child Array containing the [key, value] pair from each entry of the Object
With these tools, we can effectively move from Object-to-Array, use Array methods, and either alter values by reference or even reconstruct Object from the Array using tools like Array.reduce(). These tools are also low cost because they are native to the Object data type.
Allow me to demonstrate. I’m going to create a simple Object with three attributes and values, then perform the following plan on that Object:
- convert the Object to an Array of its entries (key/value pairs)
- filter each option down to the keys we want (a & c)
- map those options to alter each key & reduce each value to a sum of the values in it
- reconstruct the Object
let objectName = {
a: [1,2,3],
b: [2,4,6],
c: [3,6,9]
};
const result = Object.entries(objectName)////value -> Array of [key, value]
.filter(value => value[0] !== 'b')//filter out the 'b' key entry
.map((value, index) => {
value[0] = value[0] + index;//adds the index to the key (makes a string)
value[1] = value[1].reduce((agg, curr) => agg + curr, 0);//reduces array of values to sum of all values
return value;
})
.reduce((agg, curr) => {
agg[curr[0]] = curr[1];//set key/value pair on initialized object
return agg;
}, {});
//result = {
// a0:6,
// c1:18
//};
This is a silly example, but it also shows the ability of these iterative tools to perform effective operations between Objects and Arrays interchangeably. The secret is typically to convert data to Arrays, make adjustments using Array methods, then end by converting to the data type of your choice. There are many real-life versions of plans like this that I execute frequently and dependably.
Which Tool Is Right For Me?
The right tool is directly connected to the job itself. In construction, we use hammers on nails and screwdrivers on screws. Development is very similar. Here’s an effective technique to find the right tool for the job:
- Identify the context – what data type(s) are we working with, from-to?
- construct a plan – what are we attempting to accomplish?
- select the tools that match the context and fit within the plan
- refactor
I’m amazed how often stage 4 is key to producing genuinely effective, succinct, and clear code. First drafts are typically sketchbook style masses of code. Second drafts determine clear patterns. The third draft (and beyond) moves towards clearly defining stages of code and lays out the process within the plan.
Conclusion
Writing clear, meaningful code is about being familiar with the tools you have available and using them effectively based on your context and plan. JavaScript commonly uses Arrays and Objects as data types. The iterative tools introduced in this post (and more that I did not cover) are beneficial to working effectively with those data types for most of our JavaScript development needs.