1

I just found out about composition in javascript, I always thought inheritance is the standard way of writing javascript. What I don't get is, if I use a class and inherit for example two methods from another class, every time I create a new instance and console log it, the object only has its "local" properties, no sign of the inherited methods, which is obvious. But, now if I do the same with composition, every time I create a new object and I console log it, every new created object now also has the methods.

Are we not duplicating code, you know, the same methods over and over again? , in different objects. Am I missing something here?

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Anthony
  • 21
  • 2

1 Answers1

0

inheritance (and repeating the OP)

An instance does not own inherited methods itself. If called with such a method, the interpreter does start looking up the prototype chain for a matching method. Once the method was found, it gets invoked within the context of the very instance which before was this method's target object.

Thus instances do not feature own slots/properties for inherited methods but rely on JavaScript's built-in automatic delegation mechanism, hence inheritance based on prototypal delegation.

class FirstLastItemList extends Array {
  // inheritance via prototype methods.
  first() { return this[0]; }
  last() { return this[this.length - 1]; }
}
const list_A_1 = new FirstLastItemList("A", "B", "C");
const list_A_2 = new FirstLastItemList(2, 3, 4, 5, 6);

const list_B_1 = new FirstLastItemList("D", "C", "B");
const list_B_2 = new FirstLastItemList(6, 5, 4, 3, 2);

console.log('list_A_1.first() :', list_A_1.first());
console.log('list_A_2.first() :', list_A_2.first());

console.log('list_B_1.last() :', list_B_1.last());
console.log('list_B_2.last() :', list_B_2.last());

console.log('\n');
console.log(
  '(list_A_1.first === list_B_2.first) ?',
  (list_A_1.first === list_B_2.first),
  '...inherited code'
);
console.log(
  '(list_A_2.last === list_B_1.last) ?',
  (list_A_2.last === list_B_1.last),
  '...inherited code'
);

console.log('\n');
console.log(
  'Object.getOwnPropertyNames(list_A_1) :',
  Object.getOwnPropertyNames(list_A_1)
);
console.log(
  'Object.getOwnPropertyNames(FirstLastItemList.prototype) :',
  Object.getOwnPropertyNames(FirstLastItemList.prototype)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

exemplary composition techniques via function based mixins (and answering the OP)

Are we not duplicating code, you know, the same methods over and over again? , in different objects. Am I missing something here?

The answer in short is ... "Yes, but one should be aware of and one also can still control the footprint or memory usage that comes with different composition techniques."

First of all, with composition one always does add (an) own property(ies) to an object. For any newly assigned method one can either provide a full blown implementation to it or just a reference to the latter.

This is true too for the two most common forms of mixin based composition, which is, any glue code based either on Object.assign or on function based mixins.

The provided example does choose the function based mixin approach in order to promote this form of code reuse (but of cause the same idea applies to its object based counterpart) ...

function firstListItemMixinWithCodeDuplication() {
  // - duplication of both, slot and implementation/code.
  this.first = () => this[0];
}

function last() {
  // implement code once ...
  return this[this.length - 1];
}
function lastListItemMixinWithSharedReference() {
  // ... then reuse via reference ...
  // - duplicated slot but shared implementation/code.
  this.last = last;
}

const list_A_1 = ["A", "B", "C"];
const list_A_2 = [2, 3, 4, 5, 6];

const list_B_1 = ["D", "C", "B"];
const list_B_2 = [6, 5, 4, 3, 2];

firstListItemMixinWithCodeDuplication.call(list_A_1);
firstListItemMixinWithCodeDuplication.call(list_A_2);

lastListItemMixinWithSharedReference.call(list_B_1);
lastListItemMixinWithSharedReference.call(list_B_2);

console.log('list_A_1.first() :', list_A_1.first());
console.log('list_A_2.first() :', list_A_2.first());

console.log('list_B_1.last() :', list_B_1.last());
console.log('list_B_2.last() :', list_B_2.last());

console.log('\n');
console.log('list_A_1.first :', list_A_1.first);
console.log('list_A_2.first :', list_A_2.first);
console.log(
  '(list_A_1.first === list_A_2.first) ?',
  (list_A_1.first === list_A_2.first),
  '...applying closed code'
);

console.log('\n');
console.log('list_B_1.last :', list_B_1.last);
console.log('list_B_2.last :', list_B_2.last);
console.log(
  '(list_B_1.last === list_B_2.last) ?',
  (list_B_1.last === list_B_2.last),
  '...applying shared code'
);

console.log('\n');
console.log(
  'Object.getOwnPropertyNames(list_A_1) :',
  Object.getOwnPropertyNames(list_A_1)
);/*

...
...

console.log(
  'Object.getOwnPropertyNames(list_B_2) :',
  Object.getOwnPropertyNames(list_B_2)
);*/
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • Are you sure the OP is talking about mixin composition? – Bergi Dec 18 '20 at 14:14
  • Not quite, to be honest. But I guess/think the OP wants to know specifically about *inherited* and *own* properties. And especially for the latter it might be interesting to know about how the implementation of an applied/assigned method or of any *composition glue code* might add to an object's/instance's memory consumption. – Peter Seliger Dec 18 '20 at 14:24
  • Maybe? I wouldn't write such a long answer on an unclear question. – Bergi Dec 18 '20 at 14:36