Confusion about Object.create() and property inheritance

Viewed 40

In the snippet below, why does boat.calls increment when I call sailboat.getHasEngine()? Also, why is sailboat.calls set to 2 after only calling getHasEngine() once?

let vehicle = {
  calls: 0,
  hasEngine: null,
  getHasEngine: function() {
    vehicle.calls = vehicle.calls + 1;
    this.calls = this.calls + 1;
    return this.hasEngine;
  },
  canFly: null,
  getCanFly: function() { 
    vehicle.calls = vehicle.calls + 1;
    this.calls = this.calls + 1;
    return this.canFly; 
  }
}

let boat = Object.create(vehicle);
boat.canFly = false;

let sailboat = Object.create(boat);
sailboat.hasEngine = false;

let fishingBoat = Object.create(boat);
fishingBoat.hasEngine = true;

console.log(vehicle.calls);
// 0
console.log(boat.calls);
// 0
console.log(sailboat.calls);
// 0

sailboat.getHasEngine();

console.log(vehicle.calls);
// 1
console.log(boat.calls);
// 1
console.log(sailboat.calls);
// 2

1 Answers

Because of this line in getHasEngine:

vehicle.calls = vehicle.calls + 1;

That's directly accessing the vehicle object. Since boat doesn't have its own calls property, it inherits it from vehicle, so boat.calls is 1.

Also, why is sailboat.calls set to 2 after only calling getHasEngine() once?

Also because of that line: It sets vehicle.calls to 1, and at that point, sailboat doesn't have its own calls, so it inherits the property. Then on the next line when you do:

this.calls = this.calls + 1;

that's reading vehicle.calls (1), adding 1 to it, and then assigning the result (2) to sailboat.calls.

Let's throw some ASCII-art at it. After creating your objects but before callling getHasEngine, you have this in memory (omitting details):

                                                          +−−−−−−−−−−+
vehicle−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−>| (object) |
                                                      |   +−−−−−−−−−−+
                                                      |   | calls: 0 |
                                                      |   +−−−−−−−−−−+
                                   +−−−−−−−−−−−−−−−+  | 
boat−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−>|   (object)    |  | 
                               |   +−−−−−−−−−−−−−−−+  | 
                               |   | [[Prototype]] |−−+ 
                               |   +−−−−−−−−−−−−−−−+     
                               |
            +−−−−−−−−−−−−−−−+  |
sailboat−−−>|   (object)    |  |
            +−−−−−−−−−−−−−−−+  |
            | [[Prototype]] |−−+
            +−−−−−−−−−−−−−−−+      

Note that neither boat nor sailboat has a calls property.

After getHasEngine is called once, you have this — note that sailboat now has a calls property (because it was created by this.calls = this.calls + 1;):

                                                          +−−−−−−−−−−+
vehicle−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−>| (object) |
                                                      |   +−−−−−−−−−−+
                                                      |   | calls: 1 |
                                                      |   +−−−−−−−−−−+
                                   +−−−−−−−−−−−−−−−+  | 
boat−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−>|   (object)    |  | 
                               |   +−−−−−−−−−−−−−−−+  | 
                               |   | [[Prototype]] |−−+ 
                               |   +−−−−−−−−−−−−−−−+     
                               |
            +−−−−−−−−−−−−−−−+  |
sailboat−−−>|   (object)    |  |
            +−−−−−−−−−−−−−−−+  |
            | [[Prototype]] |−−+
            | calls: 2      |
            +−−−−−−−−−−−−−−−+      

Here's a version with some logging to help show that a bit:

let vehicle = {
  calls: 0,
  hasEngine: null,
  getHasEngine: function() {
    console.log("updating vehicle.calls");
    vehicle.calls = vehicle.calls + 1;
    console.log("vehicle.calls is now vehicle.calls");
    console.log("About to update this.calls, does this have calls?", this.hasOwnProperty("calls"));
    this.calls = this.calls + 1;
    console.log("Just updated this.calls, does this have calls?", this.hasOwnProperty("calls"));
    return this.hasEngine;
  },
  canFly: null,
  getCanFly: function() { 
    vehicle.calls = vehicle.calls + 1;
    this.calls = this.calls + 1;
    return this.canFly; 
  }
}

let boat = Object.create(vehicle);
boat.canFly = false;

let sailboat = Object.create(boat);
sailboat.hasEngine = false;

let fishingBoat = Object.create(boat);
fishingBoat.hasEngine = true;

console.log(vehicle.calls);  // 0
console.log(boat.calls);     // 0
console.log("boat has calls?", boat.hasOwnProperty("calls"));
console.log(sailboat.calls); // 0
console.log("sailboat has calls?", sailboat.hasOwnProperty("calls"));

sailboat.getHasEngine();

console.log(vehicle.calls);  // 1
console.log(boat.calls);     // 1
console.log("boat has calls?", boat.hasOwnProperty("calls"));
console.log(sailboat.calls); // 2
console.log("sailboat has calls?", sailboat.hasOwnProperty("calls"));
.as-console-wrapper {
  max-height: 100% !important;
}

If you remove the vehicle.calls = line, you see it stop happening:

let vehicle = {
  calls: 0,
  hasEngine: null,
  getHasEngine: function() {
    //vehicle.calls = vehicle.calls + 1;
    this.calls = this.calls + 1;
    return this.hasEngine;
  },
  canFly: null,
  getCanFly: function() { 
    //vehicle.calls = vehicle.calls + 1;
    this.calls = this.calls + 1;
    return this.canFly; 
  }
}

let boat = Object.create(vehicle);
boat.canFly = false;

let sailboat = Object.create(boat);
sailboat.hasEngine = false;

let fishingBoat = Object.create(boat);
fishingBoat.hasEngine = true;

console.log(vehicle.calls);  // 0
console.log(boat.calls);     // 0
console.log(sailboat.calls); // 0

sailboat.getHasEngine();

console.log(vehicle.calls);  // 0
console.log(boat.calls);     // 0
console.log(sailboat.calls); // 1

Related