3

I'm not sure on the best approach to have object properties that are individual for each object in a OLOO inheritance chain.

Check this fiddle or consider the following code: http://jsfiddle.net/HB7LU/19413/

Parent = {
    array: [],

    add: function(element) {
        this.array.push(element + this.array.length.toString());
        return this;
    },

    getAll: function() {
        return this.array;
    }
};

Child = Object.create(Parent, {
    removeAllButOne: { value: function() {
        this.array.splice(1);
        return this;
    }}
});

foo = Object.create(Parent);
foo.add('foo');

bar = Object.create(Child);
bar.add('bar');

In the fiddle a click on the foo or bar text will call the foo.add(...) or bar.add(...) function to add an element to the objects array, resulting in one extra <p> tag in the output.
The result is not what I want. Both foo and bar share the same array. But its easy to understand what happens, if we look up the object inheritance we can see the following: inheritance

Ok then, what can I do go get around this? There were two options that came to my mind:

Option 1) http://jsfiddle.net/HB7LU/19419/

Parent = function() {
    return {
        array: [],

        add: function(element) {
            this.array.push(element + this.array.length.toString());
            return this;
        },

        getAll: function() {
            return this.array;
        }
    };
};

Child = Object.create(Parent(), {
    removeAllButOne: { value: function() {
        this.array.splice(1);
        return this;
    }}
});

foo = Object.create(Parent());
foo.add('foo');

bar = Object.create(Child);
bar.add('bar');

This would create a new Parent object, creating all the functions of the Parent object each time a Parent object is created or a child "inherits" from a (new) Parent object. While this solves the problem I had, it seems like a bad idea to always recreate the same functions over and over again for each child type object.

Option 2) http://jsfiddle.net/HB7LU/19420/

Parent = Object.create({
    add: function(element) {
        this.array.push(element + this.array.length.toString());
        return this;
    },

    getAll: function() {
        return this.array;
    }
}, {
    ctor: { value: function(someArgs) {
        this.array = [];
        // maybe use someArgs
        return this;
    }}
});

Child = Object.create(Parent, {
    removeAllButOne: { value: function() {
        this.array.splice(1);
        return this;
    }}
});

foo = Object.create(Parent).ctor();
foo.add('foo');

bar = Object.create(Child).ctor();
bar.add('bar');

This seems to also solve the problem but avoids the recreation of the Parent object and its functions. So is this the way to go? What if I had multiple children in the inheritance chain that also have private properties?

Something like this?

Child = Object.create(Parent, {
    ctor: { value: function(someArgs) {
        this.__proto__.ctor(someArgs);
        this.otherPrivate = {};
        // maybe use someArgs
        return this;
    }},

    removeAllButOne: { value: function() {
        this.array.splice(1);
        return this;
    }}
});

Children would be shadowing the parent ctor with their own function... but in their ctor function they could call the parents ctor to not break functionality.

Thoughts and advice is highly appreciated, thanks!

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
swent
  • 148
  • 2
  • 9
  • possible duplicate of [Crockford's Prototypal inheritance - Issues with nested objects](http://stackoverflow.com/q/10131052/1048572). Yes, use constructors. – Bergi Oct 31 '15 at 18:31
  • If you're willing to use a library, [stampit](https://github.com/stampit-org/stampit) may help out. – joews Oct 31 '15 at 19:12

2 Answers2

2

Easiest way is to use Constructors so array is always created as an own property on the instance

// define Parent
function Parent() {
    this.array = []; // array will be an instance property
}
Parent.prototype = {}; // inherit all the goodies from Object.prototype
Object.assign(Parent.prototype, { // using `Object.assign` for shorthand
    add: function (element) {
        this.array.push(element + this.array.length.toString());
        return this;
    },
    getAll: function () {
        return this.array;
    }
});

// define Child
function Child() {
    Parent.apply(this); // apply Parent constructor to the instance
}
Child.prototype = Object.create(Parent.prototype); // inherit Parent's prototype chain
Object.assign(Child.prototype, {
    removeAllButOne: function () {
        this.array.splice(1);
        return this;
    }
});

Now have

var a = new Child(),
    b = new Child();
a.array === b.array; // false

You could also write this using ES 6's classes, but that is just syntactic sugar for what I've written above and will result in the same structures.

Paul S.
  • 64,864
  • 9
  • 122
  • 138
  • I don't think you really want `Parent.prototype = Object.create(null);`. Inherit from `Object` maybe? – Bergi Oct 31 '15 at 18:32
  • @Bergi when setting up my own structures I usually like having control over the whole chain. Is there some advantage to inheriting from `{}` instead of `Object.create(null)`? `typeof (new Parent); // "object"` in either case. If it's meant to be concatenated with a _String_ or something then (again personally) I prefer an _Error_ telling me I've forgotten to implement a _toString_ than a silent `"[object Object]"` – Paul S. Oct 31 '15 at 18:39
  • 2
    I think `.hasOwnProperty()` and `.isPrototypeOf()` from `Object.prototype` are quite valuable in pure inheritance (also, `instanceof Object`). But sure, it's up to you whether you want `Object` or not. I'd bet there are many libraries that will break when you use non-`Object`-objects everywhere :-) – Bergi Oct 31 '15 at 18:45
  • Yes, I can imagine breaking libraries everywhere, I'll update for OP. Back to personal preference though, I never trust `hasOwnProperty` except via `Object.prototype.hasOwnProperty` (maybe I'm paranoid) and haven't really needed `.isPrototypeOf` over `instanceof` before – Paul S. Oct 31 '15 at 18:49
  • I believe the whole idea behind OLOO is that constructors should be avoided. – joews Oct 31 '15 at 19:02
  • 1
    @PaulS.: Yeah, my personal preference is to use `hasOwnProperty.call` as well, but I rarely see it elsewhere. – Bergi Oct 31 '15 at 19:25
  • @joews: In the "pure" idea of OLOO there is no `array: []` in the `Parent`, instead you do `var instance = Object.create(Parent); instance.array = [];`. To avoid repeating ourselves, we use factories (or wait, did someone call that "constructor"?). – Bergi Oct 31 '15 at 19:26
  • @Bergi with the goal of removing `obj.constuctor.prototype` indirection, so factories are not quite equivalent. – joews Oct 31 '15 at 19:34
  • @joews if you don't define _constructor_ on the _prototype_ you cannot access `obj.constructor` in the way you describe (try with the code here, you will get `Object` because it's inheriting from `Object.prototype` but not `Child` or `Parent` because we didn't define a _constructor_ property) – Paul S. Oct 31 '15 at 19:37
  • @joews: I would not call that an indirection. The factory somehow has to know which prototype to create the object from, and a public `.prototype` property is better than a hidden closure variable. Oh, and I didn't mean to limit the term "constructor" to functions that are called with `new`. – Bergi Oct 31 '15 at 19:37
  • @joews: There are many looks this pattern can take, e.g. [this one](http://stackoverflow.com/a/11243549/1048572). Now make `.create` implicitly call a `.constructor()` method of the object if there is one :-) – Bergi Oct 31 '15 at 19:48
  • @Bergi by indirection I mean that not all properties can be reached by traversing up `prototype` properties - it can be harder to explain where any given property is defined. E.g. in this code I can't even find the path from `a` to `getAll`. – joews Oct 31 '15 at 19:56
  • 1
    Not arguing against this pattern BTW; trying to wrap my head around arguments for/against OLOO. – joews Oct 31 '15 at 19:56
  • 1
    @joews `a` inherits an _Object_ `C` (which we called `Child.prototype`), `C` inherits an _Object_ `P` (which we called `Parent.prototype`). `P` has property _getAll_ – Paul S. Oct 31 '15 at 19:59
  • @PaulS. thanks. I had some misconceptions about `prototype`/`Object.getPrototypeOf` and `constructor`, which this discussion has helped to clear up. – joews Oct 31 '15 at 21:06
  • While this wasn't exactly what I was looking for, this answer and the bottom answer both show me the options and tradeoffs of different patterns. I'm gonna vote this for the correct answer, it helped me alot and got me thinking more about `.isPrototypeOf` and `hasOwnProperty`. – swent Nov 01 '15 at 22:37
1

OLOO favours composition over inheritance. You could use a factory method pattern with Object.assign to compose objects with simple prototype delegation:

// Composable prototype objects, or "traits"
var base = {
  add: function(element) {
    this.array.push(element + this.array.length.toString());
    return this;
  },

  getAll: function() {
    return this.array;
  }
};

var canRemoveAllButOne = {
  removeAllButOne: function() {
    this.array.splice(1);
    return this;
  }
}

// Factory functions
// You could think of these like external constructors
function createBase() {
  return Object.assign({}, base, {
    array: []
  })
}

function createComposed() {
  var base = createBase();
  return Object.assign(base, canRemoveAllButOne)
}

// Test
function log(s) {
  document.write(s + "<br>");
}

var b1 = createBase();
var b2 = createBase();
var c1 = createComposed();
var c2 = createComposed();

b1.add(1);
b1.add(2);
b2.add(9);

c1.add('a');
c2.add('b');

log(b1.getAll());
log(b2.getAll());
log(c1.getAll());
log(c2.getAll());
joews
  • 29,767
  • 10
  • 79
  • 91
  • Disclaimer: I have never tried this pattern in real code, and I don't particularly like the way that `base` and `canRemoveAllButOne` assume the presence of `this.array` without creating it themselves. – joews Oct 31 '15 at 19:28
  • I thought OLOO favours behavior delegation while still maintaining and using the prototype link. But in your code you're not using the prototype link in the usual way. EDIT: strike that, I think I understand now. – swent Nov 01 '15 at 22:24
  • No, you're right. I had somehow conflated OLOO with "concatenative inheritance" (which does go hand-in-hand with OLOO). A real OLOO approach would look like `Object.assign(Object.create(parent), childAttributes)`. I'll update the answer later to make this clear. – joews Nov 02 '15 at 09:49