7

I use the following function to create instances of functions in JavaScript from an array of arguments:

var instantiate = function (instantiate) {
    return function (constructor, args, prototype) {
        "use strict";

        if (prototype) {
            var proto = constructor.prototype;
            constructor.prototype = prototype;
        }

        var instance = instantiate(constructor, args);
        if (proto) constructor.prototype = proto;
        return instance;
    };
}(Function.prototype.apply.bind(function () {
    var args = Array.prototype.slice.call(arguments);
    var constructor = Function.prototype.bind.apply(this, [null].concat(args));
    return new constructor;
}));

Using the above function you can create instances as follows (see the fiddle):

var f = instantiate(F, [], G.prototype);

alert(f instanceof F); // false
alert(f instanceof G); // true

f.alert(); // F

function F() {
    this.alert = function () {
        alert("F");
    };
}

function G() {
    this.alert = function () {
        alert("G");
    };
}

The above code works for user built constructors like F. However it doesn't work for native constructors like Array for obvious security reasons. You may always create an array and then change its __proto__ property but I am using this code in Rhino so it won't work there. Is there any other way to achieve the same result in JavaScript?

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299

2 Answers2

7

You can't fully subclass an array.

However, you can use Object.create to remove a lot of complexity from your current code (ex).

Maël Nison
  • 7,055
  • 7
  • 46
  • 77
  • How would you use `Object.create` to remove complexity from my code? I would appreciate it if you could write a demo. – Aadit M Shah Aug 07 '12 at 14:21
  • I have added an example link to my answer. Please note that it doesn't work on Ideone's rhino version (but it works fine with mine, 1.7r3). – Maël Nison Aug 07 '12 at 14:27
  • Your method works for most constructors. However it causes [problems](http://jsfiddle.net/MMp5u/2/) when a factory function is used as a constructor (e.g. `Array`). Using `Object.create(Array.prototype)` creates an object which is an `instanceof` the constructor `Array`. However it's not actually an array. My function may seem a little more complex however it [avoids](http://jsfiddle.net/MMp5u/1/) the problems presented by your function. – Aadit M Shah Aug 07 '12 at 15:43
  • It is indeed better. Your script has two problems: it still doesn't work well with [factory constructors](http://jsfiddle.net/MMp5u/9/) (mine [does](http://jsfiddle.net/MMp5u/10/)), and neither does it work well with an [array-like structure](http://jsfiddle.net/MMp5u/11/) as the second argument (e.g. `arguments`). Mine [handles](http://jsfiddle.net/MMp5u/12/) that too. – Aadit M Shah Aug 07 '12 at 19:42
  • Challenge accepted ! Here are some minor adjustements which fulfill your requirements : http://ideone.com/EWAuv (basically, returns the constructor return value, and replaces the call to concat by an additional bind). – Maël Nison Aug 08 '12 at 09:31
  • That's good. Now you can directly pass `arguments` as the second parameter to your function, and the function `f` also works. However that still doesn't solve all the problems. When you return the constructor's return value you forgot to account for the fact that we are trying to change it's internal `[[proto]]` property. This is the difference between [your script](http://jsfiddle.net/MMp5u/14/) and [mine](http://jsfiddle.net/MMp5u/15/). – Aadit M Shah Aug 08 '12 at 10:53
  • Were you able to find a solution? The bounty ends in 16 hours. – Aadit M Shah Aug 08 '12 at 15:37
  • You can get `true` by replacing `f.__proto__ = F.prototype` by `f.__proto__ = this.constructor.prototype`. I'm curious about a real use case of this inheritance. Furthermore, please note that `__proto__` is non-standard, and that there is no way to emulate its behavior with current ES standards. – Maël Nison Aug 09 '12 at 09:29
  • Indeed you can get `true` by replacing `f.__proto__ = F.prototype` with `f.__proto__ = this.constructor.prototype`. However the details of the implementation of `instantiate` should be transparent. The person who writes the function `F` should not be forced to write `this.constructor.prototype` instead of `F.prototype`. Plus using `this.constructor.prototype` would cause problems if the function `F` was used as a mixin and the `this` pointer was changed using `call`, `apply` or `bind`. In general do not force the user to rewrite his function. Just modify the `instantiate` function. – Aadit M Shah Aug 09 '12 at 11:07
  • I know that `__proto__` is non-standard. However it's implemented in all the major JavaScript engines besides Rhino, and IMHO the actual implementation of a feature matters more than a standard which is used as a model for the implementation (e.g. TCP/IP is more important than the OSI model). Beside, the `__proto__` property is so widely used that won't be removed anytime soon. Also `__proto__` [may be standardized](http://stackoverflow.com/q/10476560/783743) in ES Harmony. – Aadit M Shah Aug 09 '12 at 11:15
  • As for a real use case for this type of inheritance: it's an atomic operation which sets the internal `[[proto]]` property of a single `instance` of a `constructor` at the time of instantiation. Hence it can be used to securely subclass a native object like `Array`, which otherwise doesn't allow it's `prototype` to be replaced, in engines like Rhino which do not support the `__proto__` property. For this purpose I opened an [issue](https://github.com/ringo/ringojs/issues/181) on RingoJS to test this feature in the wild. It also reduces the need to use the `__proto__` property which is great. – Aadit M Shah Aug 09 '12 at 11:24
  • There are half a dozen or more links to jsfiddle and/or ideone in the comment thread. Would you mind putting the code example into the answer itself before those comments/links disappear? – Cypher Jul 01 '15 at 18:40
3

I don't think you are achieving what you are intending here. First in your F and G functions you are defining an alert function on the this object. This means every time you instantiate an object a new function object will be created and assigned to alert. This is not what you want, you need to define alert on the prototype of F and G.

function F() { }

F.prototype.alert = function() {
    alert("F");
};

function G() { }

G.prototype.alert = function() {
    alert("G");
};  

However you still have an issue in your instantiate function. If you call it the way you have

var f = instantiate(F, [], G.prototype);

all you are doing is setting f's prototype to G.prototype, which is not what I think you want. I'm assuming that if you instantiate an F object then you would want to be able to call all of the functions defined on F.prototype, but the way things stand this is not the case.

function F() { }

F.prototype.alert = function() {
    alert("F");
};

F.prototype.foo = function() {
    alert("F foo");
};

function G() { }

G.prototype.alert = function() {
    alert("G");
};  


var f = instantiate(F, [], G.prototype);
f.foo(); // error!

The reason for the error here is like I said you just assign f's prototype to G.prototype and G.prototype does not have a foo function defined.

If you are looking to do inheritance in this way take a look at John Resig's blog he has a nice implemantation: http://ejohn.org/blog/simple-javascript-inheritance/

Also Douglas Crockford has put together some good examples: http://www.crockford.com/javascript/inheritance.html

BenMorel
  • 34,448
  • 50
  • 182
  • 322
hackattack
  • 1,087
  • 6
  • 9
  • Haha. Actually that's precisely what I want. I created an `instantiate` function which accepts a `constructor` function and an `args` list and returns an `instance` of the `constructor`. If you pass an optional `prototype` object as the third parameter then it sets the internal `[[proto]]` property of the `instance` to that `prototype`. This is the intended behavior. So when I call `instantiate(F, [], G.prototype)` then it should create an `instance` of `F` but it should use `G.prototype` instead of `F.prototype` as the `prototype` of the `instance`. – Aadit M Shah Aug 09 '12 at 02:10
  • The reason I wrote the `alert` function inside `F` and `G` is to show the difference that although `f` is constructed by `F` (and hence `f.alert()` displays `F`) it's internal `[[proto]]` property is set to `G.prototype`. Hence it's an `instanceof` the function `G`. – Aadit M Shah Aug 09 '12 at 02:14
  • If you think about it it would be counter-intuitive to have an optional third `prototype` parameter if I never wanted to change the internal `[[proto]]` property of `f` from `F.prototype` to `G.prototype`. Since it's optional if I had left it out then `f` would inherit from `F.prototype` (which is the behavior you proposed is correct). However the intention is to modify the internal `[[proto]]` property of the `instance` without the need to manually modify `__proto__`. – Aadit M Shah Aug 09 '12 at 02:19
  • Also thank you for the links. However I believe my own understanding of inheritance is JavaScript is complete enough. You may read my answer on [prototype-based inheritance](http://stackoverflow.com/a/8096017/783743 "JavaScript inheritance and the constructor property - Stack Overflow") and my code for [classical OOP in JavaScript](https://gist.github.com/3091759 "Classical OOP in JavaScript — Gist"). – Aadit M Shah Aug 09 '12 at 02:23
  • There was a part of me that knew you were intending to do as you wrote. I just never saw that type of inheritance before so I was a bit misled. cheers! – hackattack Aug 09 '12 at 03:34