Home > Software design >  Getters disappear when object is returned in another object?
Getters disappear when object is returned in another object?

Time:10-31

I am coding the game Battleship and have the following code for a Ship factory function:

const Ship = (len) => {
  const length = len;
  let _hitNumber = 0;
  const hit = () => {
    _hitNumber = _hitNumber   1;
    return _hitNumber
  };
  return {
    length,
    get hitNumber() {
      return _hitNumber;
    },
    hit,
  };
};

const ship = Ship(3);
ship.hit();
console.log(ship.hitNumber);

It works as expected. If I call hit() and then examine .hitNumber it has increased. I then made a Fleet factory as shown below. It returns an array of Ships:

const Ship = (len) => {
  const length = len;
  let _hitNumber = 0;
  const hit = () => {
    _hitNumber = _hitNumber   1;
    return _hitNumber
  };
  return {
    length,
    get hitNumber() {
      return _hitNumber;
    },
    hit,
  };
};

const Fleet = () => {
  return [{
      name: "Carrier",
      ...Ship(5),
    },
    {
      name: "Battleship",
      ...Ship(4),
    },
  ]
}

const testFleet = Fleet();
testFleet[0].hit();
console.log(testFleet[0].hitNumber)

But now calling hit doesn't work:

It's as if .hitNumber is not a getter anymore or testFleet[0].hit() doesn't refer to testFleet[0]'s _hitnumber.

I'd like help understanding what is going on and whether it is the array brackets or the spread operator that is responsible. I suspect that the pitfalls outlined in this article are relevant, but my situation is different enough (and my understanding is shallow enough) that I can't quite make the connection.

CodePudding user response:

In your Ship() function, you return an object with a getter, then you later use the spread operator to copy all properties of the ship to a new object.

Behind the scenes, the getters are syntactic sugar for calls to Object.defineProperty(). Object.defineProperty() can set special settings on properties, such as calling a function when a property is access (this is a getter).

However, the spread operator simply copies properties from one object to another, and does not copy the special settings set by Object.defineProperty(). So, when the spread operator tries to copy the hitNumber property, it calls the getter once and copies the result, not the function itself.

To fix this, read this section on the MDN article for Object.assign(). It provides a function to copy getters (and setters) too. Here is an example with your code:

// from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#copying_accessors
function completeAssign(target, ...sources) {
  sources.forEach((source) => {
    const descriptors = Object.keys(source).reduce((descriptors, key) => {
      descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
      return descriptors;
    }, {});

    Object.getOwnPropertySymbols(source).forEach((sym) => {
      const descriptor = Object.getOwnPropertyDescriptor(source, sym);
      if (descriptor.enumerable) {
        descriptors[sym] = descriptor;
      }
    });
    Object.defineProperties(target, descriptors);
  });
  return target;
}


const Ship = (len) => {
  const length = len;
  let _hitNumber = 0;
  const hit = () => {
    _hitNumber = _hitNumber   1;
    return _hitNumber
  };

  return {
    length,
    get hitNumber() {
      return _hitNumber;
    },
    hit,
  };
};

const Fleet = () => {
  const arr = [{
      name: "Carrier",
      len: 5,
    },
    {
      name: "Battleship",
      len: 5,
    },
  ];

  arr.forEach(obj => {
    completeAssign(obj, Ship(obj.len));
  });
  return arr;
}

const fleet = Fleet();
fleet[0].hit();
console.log(fleet[0].hitNumber);

  • Related