1

In the following example i have a simple Spy object. I wanted to create another object setting the prototype with the original object, so I've used Object.create().
Now I have a new object, which have just some properties from the original ('code' and 'breath' method). All the other properties (objects - 'name' and arrays - 'enemies') are in the _proto_ object, which i can use, because they are delegated to the original object. So far so good.

The tricky part is that if I change anything in the AnotherSpy included in the _proto_ object (the object name for example), those changes will be reflected in all the objects created from the original spy, including himself!

I also tried create a new object with using JSON.parse(), but in this way I have a new object which only have access to the 2 things that were previously in the _proto_ object - the array of enemies and the name object, without being able to use any methods of the original object (the 'breath' method).

let Spy = {
  code: '007',
  enemies: ['Dr.No'],
  fullName: {
    firstName: 'James',
    lastName: 'Bond'
  },
  breath: function() {
    console.log('im breathing..')
  }
}

// original Spy breathing
Spy.breath(); // ok, he breaths

// create a new object with Object.create()
let OtherSpy = Object.create(Spy);

console.log(OtherSpy) // have direct access to properties 'code' and function 'breath' and all the others throught the __proto__ object

// Make OtherSpy breath
OtherSpy.breath(); // ok, he is breathing

// so far so good. Lets change the property and function on the OtherSpy
OtherSpy.code = '008';
OtherSpy.breath = () => {
  console.log('im a new breathing')
};

OtherSpy.breath(); // ok, he's breathing differently

console.log(Spy.code); // 007 ok, original spy has the same code
Spy.breath() // ok, he stills breath in the same way.

// change the object 'name' of the OtherSpy
OtherSpy.fullName.firstName = 'Enemy';

// That change will reflect also on the original Spy...
console.log(Spy.fullName.firstName); // Enemy !!!!

// Trying in another way: 
let NewSpy = JSON.parse(JSON.stringify(Spy));
console.log('NewSpy')
console.log(NewSpy) // now i dont have access to methods in the original object
NewSpy.breath() // Uncaught TypeError: NewSpy.breath is not a function

It seems that all properties included in the _proto_ object are shared in all objects that use that prototype chain.

Aside from this tricky parts that would greatly appreciate an explanation, I would like to know the proper way to create an object in JavaScript (without using ES6 classes) in order to get the advantage of the prototype delegation and to be able to modify the properties and functions of the derived object without messing up with the original object nor any other derived objects.

Thanks in advance!

bruno
  • 13
  • 1
  • 4
  • Yep, that kind of mutation will only do what you want with primitives. Once you access another object, like an array, you've left the prototype chain of the original object, and are therefore mutating the array instead. –  Dec 23 '17 at 18:37
  • Oh, and JSON serialization/parsing doesn't help because it ignores inherited stuff by design. –  Dec 23 '17 at 18:38
  • 1
    Why not simply use a `class` that constructs a new fullName – Jonas Wilms Dec 23 '17 at 18:49
  • Every instance must have its own enemies arrays and fullname object, you cannot possibly share those. Mutating the shared one does not work. You have to initialise them when creating the instance, either in a factory function or a constructor. (And regarding the name, you just shouldn't nest the structure where it's not necessary) – Bergi Dec 23 '17 at 19:28

2 Answers2

1

Nested properties are somewhat unuseful, so you may flatten it through using getters/setters:

const Spy = {
  firstName: "Agent",
  lastName: "Unnamed",

  breath(){
    console.log(`${this.fullName} is breathing`);
  },

  get fullName(){
     return this.firstName + " " + this.lastName;
  },
  set fullName(name){
     const [first, last] = name.split(" ");
     this.firstName = first;
     this.lastName = last;
  }
 };


  const james = Object.create(Spy);
  james.fullName = "James Bond";
  james.breath();
  console.log(james.fullName, james.firstName, james.lastName);
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • This is better, but it was also excluded as an option in the question. *"without using ES6 classes"* –  Dec 23 '17 at 18:58
  • That looks good :) So, anytime that i have objects properties that are nested objects or arrays, i can solve it by adding getters and setters to those properties to preserve the original object data. – bruno Dec 23 '17 at 19:35
  • @bruno rather don't use nested objects at all. or create one nested object for each instance ( like in the other answer) – Jonas Wilms Dec 23 '17 at 19:51
  • @bruno you are welcome :) (PS: you can reward answers by marking them as answers or givin upvotes ...) – Jonas Wilms Dec 23 '17 at 21:00
1

Another way would be to construct the name object inside of an constructor:

 function Spy(name, code, enemies){
   this.name = (([first, last]) => ({first, last}))(name.split(" "));
   this.name.toString = () => name;
   this.code = code;
   this.enemies = enemies;
 }

 Spy.prototype = {
   breath(){
     console.log(`${this.name} is breathing`);
   }
 }

Usable as:

 const james = new Spy("James Bond", "007", ["Dr. No"]);
 james.breath();
 console.log(james.name, "" + james.name);
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151