Have you ever used an Object.entries and wondered how it works? It is much simpler than you think!
Here's a basic implementation:
function objectEntries(obj) { const entries = []; for (const key in obj) { if (Object.hasOwn(obj, key)) { entries.push([key, obj[key]]); } } return entries; }
However, this code is not quite good enough - what if the object was massive? The performance of the array based approach will have to be stored in memory for the whole process of executing this function. And if you use it again? It will have to build and keep a new array in memory just the same. In the real world, this can lead to serious performance issues and at some point we would need to accommodate for performance. However, there is an elegant solution which will solve all of these problems, in which Symbol.iterator comes to the rescue!
Here is an updated snippet:
function objectEntries(obj) { return { [Symbol.iterator]() { const keys = Object.keys(obj); let index = 0; return { next() { if (indexWhy Use Symbol.iterator for Iteration?
In our initial implementation, the objectEntries function builds an array of all entries ([key, value] pairs) in memory, which can be a problem if the object has a large number of properties. Storing all the entries in an array means we have to allocate memory for every single pair in advance. This approach is fairly OK for smaller objects, but it quickly becomes inefficient and simply slow as object size grows.
In the updated code, we define [Symbol.iterator] on an object that holds the iteration logic. Let's break it down step-by-step:
Applying Symbol.iterator to custom loops
Let’s dive deeper into how these methods can provide more control over loop behavior. Each of the examples provided demonstrates a unique way to interact with array data, adding a lot of flexibility to your code. We'll explore the implications of each method and how they can be leveraged in different scenarios.
In these examples I am going to extend the Array prototype (more info on prototypes here) with the example methods to make my code easier to read. Let's jump right in!
For instance, this reverseIterator method can come in useful in something like a chat application where you might want to display the most recent messages first. Chat applications are notorious for having a LOT of data (messages in this case). Using reverseIterator, you can iterate through a list of messages and display them in the desired order without needing to create a new reversed array.
Array.prototype.reverseIterator = function() { let index = this.length - 1; return { [Symbol.iterator]: () => ({ next: () => { if (index >= 0) { return { value: this[index--], done: false }; } return { done: true }; } }) }; }; const numbers = [1, 2, 3, 4, 5]; for (const num of numbers.reverseIterator()) { console.log(num); // 5, 4, 3, 2, 1 }
This unique method enables you to iterate through an array while making sure that only unique values are yielded. This is super useful for eliminating duplicates on the fly, without filtering it in advance and using more memory.
Array.prototype.unique = function() { const seen = new Set(); return { [Symbol.iterator]: () => ({ next: () => { for (let i = 0; iThe chunk method below can be useful when dealing with large datasets, you can process them in smaller chunks to reduce memory usage and improve performance. Let's say you are importing data from something like a CSV file,you can read and process it in more scalable segments. Moreover, in web user interfaces, chunking can be used for pagination, allowing you to display a specific number of items per page or help you better manage an infinite loader.
Array.prototype.chunk = function(size) { let index = 0; return { [Symbol.iterator]: () => ({ next: () => { if (indexConclusion
In this article, we explored how Symbol.iterator customizes the logic and improves the efficiency of our loops. By implementing custom iterable methods on the Array.prototype (or any other iterable to that effect), we can manage memory usage effectively and control how our loop is running.
The initial example of objectEntries demonstrated how an array-based approach can lead to performance issues when handling large objects. However, by using the SYmbol.iterator, we created an efficient solution that allows us to iterate over object entries without the overhead of unnecessary memory allocation.
We also looked at several practical examples of how extending the Array.prototype can facilitate various real-world scenarios developers have to deal with on a day-to-day basis.
With these powerful tools at your disposal, you're better equipped to solve complex data handling scenarios in JavaScript with close-to-zero performance implications on your app.
Disclaimer: All resources provided are partly from the Internet. If there is any infringement of your copyright or other rights and interests, please explain the detailed reasons and provide proof of copyright or rights and interests and then send it to the email: [email protected] We will handle it for you as soon as possible.
Copyright© 2022 湘ICP备2022001581号-3