Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you create functions with custom prototypes in JavaScript?

First of all, I don't want to add methods to Function.prototype. Doing that would make them available for all functions and that's not what I'm looking for.

In JavaScript you can create objects with custom prototypes like this:

function CustomObj() {}
CustomObj.prototype = {};
CustomObj.prototype.sayFoo = function () { return 'foo' };

var myCustomObj = new CustomObj(); //=> returns an object: {}
myCusomObj.sayFoo(); //=> 'foo'

You can also create array-like objects with custom prototypes like this:

function CustomArr() {}
CustomArr.prototype = [];
CustomObj.prototype.sayFoo = function () { return 'foo' };

var myCustomArr = new CustomArr(); //=> returns an ordered object: []
myCustomArr.sayFoo(); //=> 'foo'

What I'd like to do is use some kind of constructor to create a function with its own custom prototype in the same way. However, the following does not work:

function CustomFn() {}
CustomFn.prototype = function () {};
CustomFn.prototype.sayFoo = function () { return 'foo' };

var myCustomFn = new CustomFn(); //=> PROBLEM! returns an object: {}
myCustomFn.sayFoo(); //=> 'foo'

// ^^ Here, the prototype was applied but the output was not a function.
myCustomFn(); //=> TypeError: object is not a function

So is there any way to accomplish what I'm trying to do?

UPDATE

Maybe there's another way I could be asking this question that would make it a little clearer.

There's a problem with the idea of a closure:

function makeFn() {
  var output = function () { /* do some stuff */ };
  output.foo = function () { /* do some stuff */ };
  return output;
}
var specialFn = makeFn();

Essentially, this technique gives me what I want. However, the problem is that every time I call makeFn, output.foo has to be created as a totally independent function that takes up its own memory. Gross. So I could move that method out of the closure:

var protoMethods = {
  "foo" : function () { /* do some stuff */ }
};
function makeFn() {
  var output = function () { /* do some stuff */ };
  for (var i in protoMethods) {
    Object.prototype.hasOwnProperty.call(protoMethods, i) &&
      (output[i] = protoMethods[i]);
  }
  return output;
}
var specialFn = makeFn();

But now I have to manually do an iteration every time I call makeFn which would be less efficient than if I could just assign protoMethods to be the prototype of output. So, with this new update, any ideas?

like image 764
Stupid Stupid Avatar asked May 16 '13 19:05

Stupid Stupid


People also ask

Do functions have prototypes JavaScript?

In JavaScript, all functions have a property named prototype . When you call a function as a constructor, this property is set as the prototype of the newly constructed object (by convention, in the property named __proto__ ).

How do you create a function prototype?

A function prototype begins with the keyword function, then lists the function name, its parameters (if any), and return value (if any). The prototype includes no executable code. You can use function prototypes in the following situations: When defining an ExternalType (see ExternalType part).

What are function prototypes in JavaScript?

The prototype is an object that is associated with every functions and objects by default in JavaScript, where function's prototype property is accessible and modifiable and object's prototype property (aka attribute) is not visible. Every function includes prototype object by default. Prototype in JavaScript.

Do all objects have prototypes in JavaScript?

Each and every JavaScript function will have a prototype property which is of the object type. You can define your own properties under prototype . When you will use the function as a constructor function, all the instances of it will inherit properties from the prototype object.


1 Answers

It is a tricky thing indeed, more complicated than it should be if the language was designed well...

Basically, you just can't do it cleanly in current versions. Objects other than functions can not be callable.

In future Javascript versions, you can do it with a "proxy" object that can define a "call" handler. But it is still way too complicated and contrived in my opinion.

Another way to go about it is to make your object a real function, not a custom object. Then try to set its __proto__, which is non-standard yet but works in most modern browsers, except Opera and IE 8 or less. Also maybe set its constructor property for faking instanceof checks... such hacks are quite tricky though and results will vary a lot with environments.

The following example works fine on my Firefox: http://jsfiddle.net/Q3422/2/

function MyFun() {
    if (!this || this==window) {
        return new MyFun();
    }

    var f = function() {
        return "thanks for calling!";
    }
    f.__proto__ = MyFun.prototype;
    f.constructor = MyFun;

    return f;
}

MyFun.prototype = {
    foo: function() {
        return "foo:" + this();
    },
    __proto__: Function.prototype
};

var f = new MyFun();
alert("proto method:"+f.foo()); // try our prototype methods
alert("function method:"+f.call()); // try standard function methods
alert("function call:"+f()); // try use as a function
alert('typeof:' + typeof f); // "function", not "object". No way around it in current js versions
alert('is MyFun:' + (f instanceof MyFun)); // true
alert('is Function:' + (f instanceof Function)); // true

Just wanted to add that you should not be worried about "copying" functions to each instance of your objects. The function itself is an object, so is never really copied, nor is it recompiled or anything. It does not waste memory, except for the function object reference itself and any closure variables.

Iterating over the prototype to copy it should not concern you as well, I guess you will not have a gazillion methods.

So your own last solution is probably the best if you need to support environments where proto is not settable, and you are not worried that your prototype might get extended after some objects already got created and they may not pick up the changes.

like image 188
user2451227 Avatar answered Sep 18 '22 09:09

user2451227