13

I'm new to JavaScript. Just a question on using the spread operator on a class function. An example:

let personA = {
  name: "Tom",
  testFunction: function() {
    // ...
  }
};
let newArray = [];
newArray.push({ ...personA });
console.log(newArray);

And the output is:

[{ name: 'Tom', testFunction: F}]

But if I use a class, such as:

class Person {
    constructor(name) { 
        this.name = name;
    }
    testFunction() {
       
    }
}

let personA = new Person("Tom");
let newArray= [];
newArray.push({...personA});
console.log(newArray);

The output is:

[{ name: 'Tom'}]

So the function is missing. Isn't everything in JS an object? So why can I use the rest operator to get the method when using object literals but not with a class?

Boann
  • 48,794
  • 16
  • 117
  • 146
  • 4
    Because the `name` is a member of an *instance* of the class and `testFunction` is a member of the *prototype* of the class. – gman Sep 23 '19 at 08:38
  • @gman that's only partly correct (larger comment on your answer). – Alnitak Sep 23 '19 at 08:49

3 Answers3

20

Object spread only copies enumerable own properties:

It copies own enumerable properties from a provided object onto a new object.

With

class Person {
    constructor(name) { 
        this.name = name;
    }
    testFunction() {

    }
}

the testFunction is on Person.prototype, not on a Person instance, so it doesn't get copied; it's not an own property.

class Person {
    constructor(name) { 
        this.name = name;
    }
    testFunction() {

    }
}
let personA = new Person("Tom");

console.log(Person.prototype.hasOwnProperty('testFunction'));
console.log(personA.hasOwnProperty('testFunction'));

If you assign testFunction to the instance in the constructor, it'll get copied:

class Person {
  constructor(name) {
    this.name = name;
    this.testFunction = this.testFunction;
  }
  testFunction() {

  }
}
let personA = new Person("Tom");
console.log(personA.hasOwnProperty('testFunction'));
let newArray = [];
newArray.push({ ...personA });
console.log(newArray);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • so is Object.Assign() the only way to properly merge two class instances without having the extra line in the constructor? Spread syntax is just out of the question? – Felipe Apr 11 '21 at 08:20
  • 2
    That's right. Spread can only be used to create an entirely new plain object literal. It can't be used to add onto existing objects or to assign to the `.prototype` of a class, unless you use `Object.assign` with it. Only `Object.assign` can assign all properties of one object to another, where the other object *already exists*. – CertainPerformance Apr 11 '21 at 13:44
1

The spread operator ... only gets the enumerable properties on the object itself.

name is a property of an instance of the class and testFunction is a property of the prototype of the class so being that testFunction is not actually a property of personA the spread operator doesn't deal with it.

I just copied and pasted your code into the JavaScript console then typed personA enter which made personA inspectable.

Expanding it you can see name is a property of personA (or rather a property of the object that personA references) but testFunction and constructor are properties of the class prototype for Person

enter image description here

Note that some people suggest I say something about 'own' properties. I find that to be nonsense.

Here's an example of what I mean

a = { fruit: 'banana', collection: 'bunch' };
b = { animal: 'dog', group: 'pack' };

c = {...a}

What is the object c references? It's a new object with properties 'fruit' and 'collection'. Do need to mention that b's properties are not considered? NO!

Similarly the same holds true for the object personA references. That object does not have enumerable properties testFunction nor constructor. They are entirely irrelevent as they are not part of the object itself. They are on a separate object entirely as pointed out by the debugger screenshot.

gman
  • 100,619
  • 31
  • 269
  • 393
  • only half correct - it's more strictly that the class method is not an _enumerable own property_ of the instance, but that just happens to also be true of properties inherited from the prototype. – Alnitak Sep 23 '19 at 08:48
  • the ultimate definition of the spread operator is that it works on an Object's _enumerable own properties_. That's the real "why". Saying that the observed behaviour is because the method is on the prototype is only half the story. You're writing like it's A => B but it's really A => X => B and you didn't mention X. – Alnitak Sep 24 '19 at 12:20
  • No need for an example, because I think the accepted answer says it perfectly. Spread only works on own enumerable properties, methods are on the prototype and therefore not own enumerable properties, therefore they don't appear. – Alnitak Sep 24 '19 at 15:37
  • no, your answer completely fails to mention that spread only works on own enumerable properties. That knowledge is _assumed_. OTOH, it's explicitly stated in the first sentence of the accepted answer. – Alnitak Sep 24 '19 at 15:42
  • I still have an issue with `own` properties. There's really no such thing as `own` properties. There's properties on the object (the one personA references) and properties on the prototype. They really have absolutely nothing to do with each other. The concept of `own` is left over from `for in` where its iterator went over enumerable properties in other objects (in the objects of the prototype chain). The spec itself says nothing of "own" properties in relation to the spread operator. – gman Sep 24 '19 at 16:07
  • the language spec itself still makes a distinction between "own properties" and "properties" (that might be inherited). The spec is indeed a little opaque w.r.t. the spread operator, but I think it comes down to the behaviour of the default `@@iterator` compared to the one used by `for .. in`. – Alnitak Sep 24 '19 at 20:42
  • The spec says nothing about "own" properties. It has no need to. An object by defintion only has its own properties. Things like following the prototype chain are an exception that would need to be detailed. Since there is no exception for the spread operater nothing is mentioned.Those exceptions for the `for .. in` expression are mentoned but not for spread. – gman Sep 24 '19 at 21:16
0

The name is part of the class when initializing the constructor as this

constructor(name) { 
    this.name=name
}

When want to create an instance of the class it should be added the name as you want to same the object you should add the function in the constructor. If you want the same shape add object it should add the function inside the constructor:

constructor(name) { 
    this.name=name;
    this.testFunction = this.testFunction;
}
testFunction(){ //what you want }

This it same the object.

If still have issue about this tell me.

Adder
  • 5,708
  • 1
  • 28
  • 56
Mohammed Al-Reai
  • 2,344
  • 14
  • 18