Is there a similar concept to "Symbol.iterator" for the object spread syntax { ...obj } in JavaScript?

One symbol that is widely recognized is Symbol.iterator. When this symbol is defined as a generator function property on an object, it enables the object to be utilized in a [...object] syntax. For example, you can do something like this:

const newArray = [ ...foo, ...object, ...bar ];

However, I have not been able to find a similar functionality that allows for this:

const newObject = { ...foo, ...object, ...etc };

or using Object.assign with non-own properties. An example of this is seen in instances of ES6 classes with accessors like get prop() / set prop() - they are defined on the .prototype property of the class constructor:

const C = class {
  #num = 42;
  #str = 'aaa';
  
  get num() { return this.#num; }
  set num(val) { /* validate new value */ this.#num = val; return true; }

  get str() { return this.#num; }
  set str(val) { /* validate new value */ this.#num = val; return true; }
}

const obj = new C();

Now, obj has validation for obj.num = ... and obj.str = .... However, it cannot be used in {...foo, ...obj}, or in Object.assign(foo, obj), due to the accessors being on the prototype. While Proxies can be spread and property access trapped for validation, my goal is to ensure that accessing obj.prop closely matches the performance of accessing plain object properties. Consider the following:

const p = new Proxy({ num: 0 }, {});

suite.add('instance with class-accessors', () => obj.num++);
suite.add('proxy', () => p.num++);
suite.run();

instance with class-accessors x ***235***,971,841 ops/sec ±0.20% (86 runs sampled)
proxy x ***1***,014,238 ops/sec ±1.91% (84 runs sampled)

This shows a significant difference in speed! So, is there a way to allow { ...obj } / Object.assign for instances with prototype-based getters?

I've experimented by defining *[Symbol.iterator]() {...} in the class, yielding pairs in the style of Object.entries, but it did not work. I checked MDN's "Well known symbols" and didn't find any useful solutions. I expected to find something like Symbol.entries that could control the spread/assign behavior and make working with instances containing getters more transparent.

Answer №1

Unfortunately, no, there is not an easy way to achieve this with object literal spread syntax. It simply grabs all enumerable own properties and that's the extent of its functionality.

One common workaround involves creating a getter method within a class to serialize the object in a way that can be spread as desired. This approach is often used for JSON conversion as well:

class MyClass {
  #num = 42;
  #str = 'example';
  get num() { return this.#num; }
  get str() { return this.#str; }
  
  toJSON() {
    return {num: this.#num, str: this.#str};
  }
}

const originalObject = new MyClass();

const modifiedObject = { key: 'value', ...originalObject.toJSON(), anotherKey: 'anotherValue' };
console.log(modifiedObject);
console.log(JSON.stringify(originalObject));

Answer №2

Another approach would involve defining the getter and setter from the class prototype to the object using Object.defineProperty(). This way, the spread syntax can be utilized. Here is an example implementation:

function makeProtoGSetterEnumerable(target=this,classF=this?.constructor??target.constructor, bindGet=true){

    let descriptors= Object.getOwnPropertyDescriptors(classF.prototype)

    const log=false;
    if(log)console.log("before (Only proto holds the g|setter)\ntarget Desc:",Object.getOwnPropertyDescriptors(target),"\nproto Desc",descriptors);

    //You don't want to modify certain props on class e.g. .constructor
    let not=['constructor'];
    const desc_entries=Object.entries(descriptors).filter( ([prop_key])=> false===not.includes(prop_key)  )

    for( const [prop_key,desc] of desc_entries ){
        //setting unconfigurable prop raises an Error
        if(desc.configurable===false || desc.enumerable===true ){continue }
        const new_desc={
            ...desc,
            get: desc?.get,
            set: desc?.set,
            enumerable:true,
            configurable:true,
        }
        if(bindGet){ 
          //Only for better preview in web console, no need to click (...) , but otherwise uneccessary & slightly more inefficent 
          new_desc.get=  new_desc.get.bind(target);
        }
        Object.defineProperty( target , prop_key ,new_desc )            
    }
    if(!log){return}
    const desc_after_proto=Object.getOwnPropertyDescriptors(target.constructor.prototype);
    const desc_after=Object.getOwnPropertyDescriptors(target);
    console.log("after (Both proto hold the g|setter)\ntarget Desc:",desc_after,"\nproto Desc",desc_after_proto)
  }

This method can be applied within the constructor or on a specific object. Consider the following class:

const C = class {
  #num = 42;
  #str = 'aaa';
    
  // constructor(){ makeProtoGSetterEnumerable(this) }
  get num() { return this.#num; }
  set num(val) { /* validate new value */ this.#num = val;}

  get str() { return this.#str; }
  set str(val) { /* validate new value */ this.#str = val;}
}

const obj = new C();
console.log( obj , {...obj} )

// {#num:42,#str:'aaa'} {}  //Spread is empty

makeProtoGSetterEnumerable(obj);
console.log(obj, ({...obj}))
// {num: 42, str: 'aaa', #num: 42, #str: 'aaa'} {num: 42, str: 'aaa'} 
//Now, the spread operation works correctly and only includes non-private getters

//In Chrome, viewing and editing of .#props in the web console is allowed, but they function as intended in modules 

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Incorporate a background image property using Jquery

Can anyone help me with adding the css background-image:url("xxxxx") property to my code? jQuery('#'+$trackID+' .sc_thumb').attr('src',$thumb_url); jQuery('#'+$trackID+' .sc_container').css('display& ...

Assurances made for receiving and handling an array

I'm fairly new to working with promises and I'm struggling to grasp the concept. Can someone help me understand how to implement the following logic in my code? Currently, I am building a Web service using Node.js and Express to fetch song data ...

Tips on passing an integer to an HTML page with Express.js routing

I've got this Express code that currently redirects to the views/index.html page. What I need now is to send some integers to index.html when a specific function executes in this code. Any suggestions on the most efficient way to accomplish this? cons ...

What is the process for passing a component as a parameter to a function within a different component?

Within my scenario, I have a series of components '1,2,3,...' imported into another primary component 'A'. Within component 'A', certain operations are performed and a filtered component is then returned from the list of compo ...

execute a function when an image is clicked using jQuery

How can I create an onclick event for the variable imageCatuaba? function setCatuaba(map) { var imageCatuaba = { url: 'images/catuskov.1.png', origin: new google.maps.Point(0, 0), anchor: new google.maps.Point(0, 32) }; I'm ...

Retrieving Outdated Information through the Express Framework

Whenever I make a fetch request to a node express server, it sometimes returns old data that was previously fetched from the same endpoint. This issue occurs sporadically but seems to happen more often than not. Even after disabling Cache-Control in the f ...

Integrate PEM certificate into an Http Request with AngularJS/NodeJS

Currently, I am working on an application that requires retrieving data from a REST endpoint within an encrypted network. Accessing this data is only possible by physically being present (which is currently not an option) or using a PEM certificate provide ...

Is it necessary to release a new library version for non-functional updates?

As the maintainer of several JavaScript libraries, I often find myself needing to update dependencies that don't necessarily require any functional changes. This is especially true when my library is not impacted by a breaking change in one of its dep ...

Whenever I try to send an email in Node.js, I encounter 404 errors. Additionally,

I have an Angular application with a form that makes AJAX requests. Emailing works fine, but no matter what I set the response to, I get an error for the path '/send'. I assume Node.js expects the path '/send' to render a template or da ...

What is the best way to combine two arrays of objects into a nested tree format?

I am facing a challenge with 2 arrays of objects. Let's call them Array A and Array B. [ { value: 'Node 1', id: 1, childs: [ { value: 'Node 2', id : 2, ...

Exploring the potential of jQuery and AJAX for dynamic content generation:

I have developed a jQuery plugin that pulls data from a JSON feed (specifically YouTube) and then presents the results in a DIV. Everything is working well until I need to display the results with different configurations, such as more videos or from anoth ...

What could be the reason for the HTML canvas not displaying anything after a new element is added?

How come the HTML canvas stops showing anything after adding a new element? Here is my HTML canvas, which works perfectly fine until I add a new element to the DOM: <canvas class="id-canvas", width="1025", height="600"> ...

When the JavaScript alert appears, play audio and then pause it when the alert is closed

I am attempting to have an audio play when a JavaScript alert is triggered and stop playing the audio when the user dismisses the alert. However, I am encountering an issue where the audio does not stop when the user closes the alert. Additionally, the a ...

Sending form data from a third-party website to Django

Currently, I am running a Django website that holds user information. My goal is to host forms on external websites, such as a newsletter signup form. I want to be able to extract data from a queryset in the URL and send it back to my Django site. At the m ...

Encountering build:web failure within npm script due to TypeScript

Our project is utilizing the expo-cli as a local dependency in order to execute build:web from an npm script without requiring the global installation of expo-cli. However, when we run npm run build:web, we encounter the following exception. To isolate th ...

Creating a countdown timer for an ASP.NET web application using C#

As I work on developing a timed online test in C# ASP.NET, I encountered an issue with the countdown timer functionality. Currently, the timer is based on the local machine time, but if there is a database disconnection or system hang, it needs to resume f ...

The authentication protocol, Next Auth, consistently provides a 200 status response for the signIn function

I ran into a challenge while building my custom login page using Next Auth. The issue arises when I try to handle incorrect login data. If the login credentials are correct, I am able to successfully send JWT and redirect to /dashboard. However, when the l ...

Utilize the Bootstrap 4 class to ensure that the navigation brand image is visible solely on medium screens or larger

<header> <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <div class="container"> <a class="navbar-brand" href="#"> <img class="navbar-brand d-none d-md-block" src="img/logo.png"></ ...

When attempting to navigate to a different page in Next.js, the Cypress visit functionality may not function as

In my upcoming application, there are two main pages: Login and Cars. On the Cars page, users can click on a specific car to view more details about it. The URL format is as follows: /cars for the general cars page and /cars/car-id for the individual car p ...

Using Electron to Show a Video Stored in the Local File System Using a Custom Protocol

Currently, I'm facing challenges while using electron to develop a basic video display application. Despite my efforts, I am struggling to show a video in the renderer by correctly implementing the registerSchemesAsPrivileged method. Although there ar ...