1console.log("hello-world);
console.log("hello-world);
.container {
width: 80%;
}
<pre><code class="language-css">
.container {
width: 80%;
}
</code></pre>
1console.log("hello-world);
console.log("hello-world);
.container {
width: 80%;
}
<pre><code class="language-css">
.container {
width: 80%;
}
</code></pre>
In this blogpost, we will see ways to loop through Object keys in reverse order in JavaScript. We’ll also explore Generators, ES6 Object property ordering nuances, and how an Array and Object differ in their ordering, avoiding mutation while iteration, and a common error with for…in loop while iterating Objects. Lets dive in, and make you JavaScript Object Moon-Walking expert 🕺!
const obj = { a: 'one', b: 'two', c: 'three' };
for (const key in obj) {
console.log(`${key}: ${obj[key]}`);
}
// Output:
// a: one
// b: two
// c: three
Extract keys using Object.keys()
.
Reverse the array with reverse()
. Note that reverse()
mutates the array it acts upon. So here the array generated by Object.keys()
is mutated by reverse()
, but the object itself is untouched.
Iterate using forEach()
.
const obj = { a: 'one', b: 'two', c: 'three' };
const reversedKeys = Object.keys(obj).reverse();
reversedKeys.forEach(key => {
console.log(`${key}: ${obj[key]}`);
});
// Output:
// c: three
// b: two
// a: one
Extract keys using Object.keys()
.
Reverse the array with reverse()
. Note that reverse()
mutates the array it acts upon. So here the array generated by Object.keys()
is mutated by reverse()
, but the object itself is untouched.
Iterate using forEach()
.
const obj = { a: 'one', b: 'two', c: 'three' };
const keys = [...Object.keys(obj)];
const reversed = keys.reverse();
reversed.forEach(key => {
console.log(`${key}: ${obj[key]}`);
});
// Output:
// c: three
// b: two
// a: one
Extract keys using Object.values()
.
Reverse the array with reverse()
. Note that reverse()
mutates the array it acts upon. So here the array generated by Object.values()
is mutated by reverse()
, but the object itself is untouched.
Iterate using forEach()
.
const obj = { a: 'one', b: 'two', c: 'three' };
const reversedValues = Object.values(obj).reverse();
reversedValues.forEach(value => {
console.log(value);
});
// Output:
// three
// two
// one
Extract keys using Object.entries()
.
Reverse the array with reverse()
. Note that reverse()
mutates the array it acts upon. So here the array generated by Object.entries()
is mutated by reverse()
, but the object itself is untouched.
Iterate using forEach()
.
const obj = { a: 'one', b: 'two', c: 'three' };
const reversedEntries = Object.entries(obj).reverse();
reversedEntries.forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// Output:
// c: three
// b: two
// a: one
Generators provide a powerful way to create intricate iteration behavior, which can be useful while reverse iterating object keys. While simple loops can use break
and continue
, generators offer additional flexibility: like pausing or conditional continuation.
State Preservation: Generators maintain their state, allowing for resumption of iteration at specific points.
Custom Iteration Logic: Generators can yield values based on complex conditions, beyond simple breaks and continues.
function* reverseObjectIteratorWithPause(obj) {
const keys = Object.keys(obj).reverse(); // Reverse the keys
for (let key of keys) {
if (obj[key] === 'pause') {
yield 'Iteration paused';
} else {
yield `Key: ${key}, Value: ${obj[key]}`;
}
}
}
// Example object
const obj = { a: 1, b: 'pause', c: 3, d: 4 };
// Using the generator
const iterator = reverseObjectIteratorWithPause(obj);
console.log(iterator.next().value); // Key: d, Value: 4
console.log(iterator.next().value); // Key: c, Value: 3
console.log(iterator.next().value); // Iteration paused
console.log(iterator.next().value); // Key: b, Value: pause
reverseObjectIteratorWithPause
iterates over the object’s keys in reverse order.'pause'
, it yields a message indicating that the iteration is paused. This showcases the ability of generators to handle conditional logic within iterations.ES6 specifies property order in objects: integer keys in ascending order, followed by string and symbol keys in creation order. So they may not always be listed in the order in which you added them, and even your iteration order may not be as expected. Further, relying completely on even ES6 property order can be risky, especially when considering different JavaScript engines or older versions.
For now, lets illustrate this as per ES6 specs. Let’s consider an object with multiple numeric, string, and symbol keys.
const sym1 = Symbol('sym1');
const sym2 = Symbol('sym2');
const mixedObj = {
'30': 'numericKey1',
'10': 'numericKey2',
'b': 'stringKey1',
'a': 'stringKey2',
[sym1]: 'symbolKey1',
[sym2]: 'symbolKey2'
};
// Logs properties in the order: numeric keys in ascending order, string keys, symbol keys
console.log(Reflect.ownKeys(mixedObj));
// Output: ['10', '30', 'b', 'a', Symbol(sym1), Symbol(sym2)]
sym1
, sym2
) in order of creation as well.Arrays have a predictable iteration order, based on index positions. Object properties have a specific order as per ES6, but it’s different from arrays.
const arr = ['first', 'second', 'third'];
const obj = { '0': 'first', '2': 'third', '1': 'second' };
// Reverse Array Iteration
for (let i = arr.length - 1; i >= 0; i--) {
console.log('Array Order: ',arr[i]);
}
// Reverse Object Iteration
const keys = Object.keys(obj);
console.log("Keys: ", keys);
for (let i = keys.length - 1; i >= 0; i--) {
console.log('Object Order: ',obj[keys[i]]);
}
// Output: 👇
// Array Order: third
// Array Order: second
// Array Order: first
//
// Keys: [ '0', '1', '2' ]
// Object Order: third
// Object Order: second
// Object Order: first
arr
is index-based, whereas obj
follows ES6 property order rules.arr
and obj
demonstrates how array elements follow the index order, while object properties adhere to the specified ES6 order, which may differ from the array index order.The for...in
loop in JavaScript iterates over the enumerable properties of an object, in the order defined as per ES6 specifications. However, it’s important to note that this loop iterates over all enumerable properties, including those inherited through the prototype chain.
But, by adding the Object.hasOwnProperty
check, you can ensure that only the object’s own properties are considered.
// Define a prototype with properties
const prototypeObj = {
inheritedProperty: 'inherited value'
};
// Create a new object that inherits from prototypeObj
const obj = Object.create(prototypeObj);
obj.ownProperty1 = 'value1';
obj.ownProperty2 = 'value2';
// Using for...in loop without hasOwnProperty check
for (let key in obj) {
console.log(`${key}: ${obj[key]}`);
}
// Output includes:
// ownProperty1: value1
// ownProperty2: value2
// inheritedProperty: inherited value
// - - Revised loop with hasOwnProperty - -
// Using for...in loop with hasOwnProperty check
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(`${key}: ${obj[key]}`);
}
}
// Output includes only the object's own properties:
// ownProperty1: value1
// ownProperty2: value2
inheritedProperty
is logged alongside the object’s own properties, demonstrating how the for...in
loop includes inherited properties.inheritedProperty
is not logged, as obj.hasOwnProperty(key)
filters out properties that are not directly on obj
but are inherited from its prototype.So how do you reverse Object using for…in loop? You cant directly access index, so you’ll need to store the keys in an array, reverse this array, and then iterate over it.
const obj = { a: 'one', b: 'two', c: 'three' };
let keys = [];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
keys.reverse(); // ['c', 'b', 'a']
keys.forEach(key => console.log(`${key}: ${obj[key]}`));
// Output:
// c: three
// b: two
// a: one
When iterating over an object’s properties, adding or removing elements during the iteration can lead to confusing behaviors.
In following example, at a particular iteration, we will remove an element already iterated, being iterated and going to be iterated, and then also add an element. Lets see what this mutation while iteration gives.
const obj = { a: 1, b: 2, c: 3 };
Object.keys(obj).forEach((key, index) => {
console.log(`Before mutation: ${key}, ${obj[key]}`);
if (key === 'b') {
delete obj['a']; // Deleting already iterated property
delete obj['b']; // Deleting an existing property
delete obj['c']; // Deleting upcoming iteration property
obj['d'] = 4; // Adding a new property
}
console.log(`After mutation: ${key}, ${obj[key]}`);
});
console.log('Final object:', obj);
// Output: 👇
// Before mutation: a, 1
// After mutation: a, 1
// Before mutation: b, 2
// After mutation: b, undefined
// Before mutation: c, undefined
// After mutation: c, undefined
// Final object: { d: 4 }
Object.keys(obj)
has already produced an array with value [a,b,c]
, and we are iterating with forEach on this array of keys, but inside those iterations we are mutating elements from the Object itself. Also, once the array [a,b,c]
is produced by Object.keys()
, it is unlinked to obj
and will not reflect mutation of keys in obj
.a
) logs the current state of the object before any mutation.b
), the object is mutated: current iteration key (b
), previous iteration key (a
) and next iteration key (c
) are deleted, and 'd': 4
is added.a
is unaffected (since already iterated, b
got deleted after mutation, and that c
is deleted for the next iteration. And that d
is never iterated, since it wasn’t part of the array produced by Object.keys(obj)
.'a','b','c'
and the addition of 'd'
. Only 'd'
remains in the end.Object.keys()
and for loop is faster than Object.keys().reverse().forEach()
(where there is an additional reverse()
call).
const largeObj = {};
for (let i = 0; i < 1000_000; i++) {
largeObj[`key${i}`] = i;
}
// Technique 1: Using Object.keys() with reverse()
console.time('With reverse');
Object.keys(largeObj).reverse().forEach(key => {});
console.timeEnd('With reverse');
// Technique 2: Without using reverse()
console.time('Without reverse');
const keys = Object.keys(largeObj);
for (let i = keys.length - 1; i >= 0; i--) {
// Iteration logic here
}
console.timeEnd('Without reverse');
// Timing on my machine (for 1000_000 iterations)
// With reverse: 419.276ms
// Without reverse: 389.834ms
reverse()
method, potentially offering better performance for iterating over large objects.In the spirit of Test Driven Development ( 😁), lets test our understanding by solving a problem.
Write a function which does a “Backward Summation Safari”, where it’ll traverse the properties of a JavaScript object in reverse order, adding up only numerical values it finds the trail.
Problem (JavaScript)
/**
* Goes on a safari through an object's properties in reverse, summing all numerical values.
* @param {Object} obj - The object to explore.
* @return {number} - The total sum of numerical property values encountered.
*/
function backwardSummationSafari(obj) {
// > > > 👉 Write code here 👈 < < <
}
// Driver code
const safariObject = { zebra: 3, lion: 7, elephant: 2, garbage: 'value' };
console.log(backwardSummationSafari(safariObject));
// Expected Output: The sum of the values (3 + 7 + 2 = 12) when iterated in reverse order
// skipped pair (garbage: 'value').
Please attempt before seeing the Answer:
function backwardSummationSafari(obj) {
const keys = Object.keys(obj).reverse();
let totalSum = 0;
keys.forEach(key => {
if (typeof obj[key] === 'number') {
totalSum += obj[key];
}
});
return totalSum;
}
Explanation:
backwardSummationSafari
function grabs all the property keys of the object and reverses their order.totalSum
as 0, & iterates over the reversed keys.totalSum
.So you are an expert in Moonwalking Objects now. 🤓 Great! 🎉
But don’t stop, and keep coding! 🚀👨💻
Feel free to reach out!