We often see that the JavaScript object system is described as example-based or prototype-based, but we don’t always see what that actually means.
So today we’re going to understand how composition works in JavaScript.
One of the ways objects relate to each other is through behavior delegation. Although it’s a simple and powerful system on its own, combined with composition we can build objects based on others, creating an even more powerful system.
To understand composition, we can think of each object as a Lego piece that has defined behaviors. By joining several of them into a target object, we form more complex objects.
Let’s see how composition is implemented in JavaScript.
Object.assign(..)
Since ES2015, the language implements the Object.assign() method, which allows us to compose objects or create mixins (as some call them).
Although previously we could use this functionality in libraries like jQuery with $.extend() or in Lodash with _.assign()
The method’s syntax is pretty straightforward — it receives as many arguments as you want, but they all must be objects.
Of these arguments, the first one is the object that will be built upon (the target object). The remaining arguments are the objects that will be used to build it.
const comer = {
comer(comida = '') {console.log(`Estoy comiendo ${comida}`)}
}
const caminar = {
caminar() {console.log('estoy caminando')}
}
const taladrar = {
taladrar() {console.log('estoy taladrando')}
}
const masajear = {
masajear() {console.log('Estoy masajeando')}
}
const obrero = Object.assign({nombre: 'Juan'}, comer, caminar, taladrar)
const masajista = Object.assign({nombre: 'Juana'}, comer, caminar, masajear)
masajista.masajear() //'Estoy masajeando'
obrero.taladrar() // 'estoy taladrando'
masajista.comer('Ensalada') // 'Estoy comiendo Ensalada'
obrero.comer('Carne') // Estoy comiendo Carne
It’s important to keep in mind that:
-
Only the enumerable properties of each object are copied.
-
Composition uses the [[put]] method, so you should be aware of shadowing cases.
const obj = {
get prop() {
return this.__prop__;
},
set prop(value) {
this.__prop__ = value * 4;
},
};
var bb = {
prop: 4
};
Object.assign(obj, bb); // obj.prop = 16
Advantages
-
As we can see, it represents a simpler mental model for representing relationships between objects.
-
It improves the readability of our code.
-
It’s easier to achieve separation of concerns, allowing us to isolate functionality and reuse code (DRY).
-
We can modularize our code by breaking those large and complex objects into smaller ones.
-
Security, since each object is an independent entity.
Next week, I’ll write about functional inheritance, which will allow us to combine these concepts and see how powerful they are together.