I am learning the concepts of Composition in JS. Below is my demo code.
The moveBy
function assigns the values correctly to x
and y
.
However, the setFillColor
function does not assign the passed value to fillColor
.
What exactly is happening when the setFillColor
function is called?
const withMoveBy = (shape) => ({
moveBy: (diffX, diffY) => {
shape.dimensions.x += diffX;
shape.dimensions.y += diffY;
},
});
const withSetFillColor = (shape) => ({
setFillColor: (color) => {
console.log(shape.fillColor); // 1
shape.fillColor = color;
shape.dimensions.fillColor = color;
console.log(shape.fillColor); // 2
},
});
const shapeRectangle = (dimensions) => ({
type: 'rectangle',
fillColor: 'white',
dimensions,
});
const shapeCircle = (dimensions) => ({
type: 'circle',
fillColor: 'white',
dimensions,
});
const createShape = (type, dimensions) => {
let shape = null;
switch (type) {
case 'rectangle': {
shape = shapeRectangle(dimensions);
break;
}
case 'circle': {
shape = shapeCircle(dimensions);
break;
}
}
if (shape) {
shape = {
...shape,
...withSetFillColor(shape),
...withMoveBy(shape),
};
}
return shape;
};
let r = createShape('rectangle', {
x: 1,
y: 1,
width: 10,
height: 10,
});
let c = createShape('circle', { x: 10, y: 10, diameter: 10 });
r.moveBy(2, 3);
c.moveBy(1, 2);
r.setFillColor('red');
c.setFillColor('blue');
console.log(r);
console.log(c);
OUTPUT:
Line marked as // 1
prints white
in case of rectangle as well as circle.
Line marked as // 2
prints red
for rectangle and blue
for circle.
The final output is:
{
"type": "rectangle",
"fillColor": "white",
"dimensions": {
"x": 3,
"y": 4,
"width": 10,
"height": 10,
"fillColor": "red"
}
}
{
"type": "circle",
"fillColor": "white",
"dimensions": {
"x": 11,
"y": 12,
"diameter": 10,
"fillColor": "blue"
}
}
The fillColor
as the property of object is still white
.
However, the one inside of dimensions
has taken the correct value.
The problem stems from this assignment in createShape
- annotations by me:
// creating the "new object"
shape = {
...shape, // shallow copying of the "old object"
...withSetFillColor(shape),
...withMoveBy(shape),
};
Here, you create a new object that is composed of:
...shape
(type, fillcolor, dimensions which is an object)setFillColor
, a closure that is bound to shape
(the old object)moveBy
, a closure that is bound to shape
(the old object)After this statement is executed, you have created two shapes:
Out of the properties copied from the old object, only dimensions is a non-primitive value, so it is shared between the instances.
Then, when you call:
r.moveBy(2, 3);
it changes oldShape.dimensions
, but it's the same object as newShape.dimensions
, so it's visible in the output.
However, this call:
r.setFillColor('red');
modifies the fillColor
property of the oldShape
, which you are not seeing. It also writes to oldShape.dimensions.fillColor
, which, again, is shared between the objects, so that change is visible in both.