14

I like that in javascript, I can create a function, and then add further methods and attributes to that function

myInstance = function() {return 5}
myInstance.attr = 10

I would like to create a class to generate these objects. I assume I have to inherit from the Function base class.

In other words, I would like to:

var myInstance = new myFunctionClass()
var x = myInstance()
// x == 5

But I don't know how to create the myFunctionClass. I have tried the following, but it does not work:

var myFunctionClass = function() {Function.call(this, "return 5")}
myFunctionClass.prototype = new Function()
myInstance = new myFunctionClass()
myInstance()
// I would hope this would return 5, but instead I get
// TypeError: Property 'myInstance' of object #<Object> is not a function

I also tried the more complicated (and more proper?) inheritance method found here: How to "properly" create a custom object in JavaScript?, with no more luck. I have also tried using the util.inherits(myFunctionClass, Function) found in node.js. Still no luck

I have exhausted Google, and therefore feel that I must be missing something fundamental or obvious. Help would be greatly appreciated.

Community
  • 1
  • 1
dgreisen
  • 483
  • 5
  • 9
  • It is not possible to do this (using `[[prototype]]`) in ECMAScript 3rd Edition. I do not know if there is a tricky method in 5th Edition that allows this, or not. "Conventional" methods -- e.g. that used in jQuery -- copy the individual properties over from a "prototype", without using prototype inheritance. –  Nov 02 '11 at 23:35

3 Answers3

14

Your trying to inherit from Function. This is a right pain to do. I suggest you do the following instead

Live Example

var Proto = Object.create(Function.prototype);
Object.extend(Proto, {
  constructor: function (d) {
    console.log("construct, argument : ", d);
    this.d = d; 
    // this is your constructor logic
  },
  call: function () {
    console.log("call", this.d);
    // this get's called when you invoke the "function" that is the instance
    return "from call";
  },
  method: function () {
    console.log("method");
    // some method
    return "return from method";
  },
  // some attr
  attr: 42
});

You want to create a prototype object that forms the basis of your "class". It has your generic methods/attributes. It also has a constructor that gets invoked on object construction and a call method that gets invoked when you call the function

var functionFactory = function (proto) {
  return function () {
    var f = function () {
      return f.call.apply(f, arguments);      
    };
    Object.keys(proto).forEach(function (key) {
      f[key] = proto[key];
    });
    f.constructor.apply(f, arguments);
    return f;
  }
}

A function factory takes a prototype object and returns a factory for it. The returned function when called will give you a new function object that "inherits" from your prototype object.

var protoFactory = functionFactory(proto);
var instance = protoFactory();

Here you create your factory and then create your instance.

However this isn't proper prototypical OO. we are just shallow copying properties of a prototype into a new object. So changes to the prototype will not reflect back to the original object.

If you want real prototypical OO then you need to use a hack.

var f = function () {
  // your logic here
};
f.__proto__ = Proto;

Notice how we use the non-standard deprecated .__proto__ and we are mutating the value of [[Prototype]] at run-time which is considered evil.

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • This is almost the solution I was looking for. However, how do I access attributes of the instance from within methods? `this` does not work; I thought putting `that=this` in the constructor and then referencing `that.attribute` would work. But `that` always points to the most recently created instance, not to the instance from which I am calling the method. – dgreisen Nov 03 '11 at 16:10
  • @dgreisen `this` works inside methods and inside the constructor `this` works, and inside the `call` method `this` works. However I don't call the constructor for you, you have to do that yourself. I've edited the code so the constructor gets called – Raynos Nov 03 '11 at 16:21
  • @Raynos, Thanks for the response. My code calls the constructor, so I don't think that is the problem. Problem illustration: In live example add to constructor: `this.d=d;`; modify call to `console.log("call", this.d);`; add at end: `instance.call();`. instance.call() correctly logs 'call data'. instance() logs 'call undefined'. – dgreisen Nov 03 '11 at 17:57
  • If I understand correctly, `this` refers to a function's owner. The owner of instance.call() is instance -> `this` refers to instance. But the owner of instance() itself is window -> `this` points to window, not instance. Is there a keyword that points to instance from within instance, so I can access instance.d from instance()? – dgreisen Nov 03 '11 at 17:58
1

JS does not allow a constructor to return a function, even though functions are objects. So you cant have an instantiation of a prototype that is itself executable. (Am I right in this? please correct if I'm not, it's an interesting question).

Though you could do a factory function:

var makeCoolFunc = function() {
  var f = function() { return 5 };
  f.a = 123;
  f.b = 'hell yes!'
  return f;
};

var func = makeCoolFunc();
var x = func();
Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • 3
    "*JS does not allow a constructor to return a function*" - I don't think that's correct. You can return a function object from a constructor function. – Šime Vidas Nov 02 '11 at 23:41
  • 4
    Well, it may return it, but if you return something other than `this` then the return value wont have the constructors prototype, right? So there isn't much point. Example: http://jsfiddle.net/mDxhg/ – Alex Wayne Nov 02 '11 at 23:43
  • 2
    You can return a function from a constructor. You however cannot return an instantiation of a prototype with an internal `[[Call]]` property – Raynos Nov 02 '11 at 23:44
0

You can extend Function and pass the wanted function body as String to the super constructor. The context of the function can be accessed with arguments.callee.

Example for an observable Attribute class:

    export default class Attribute extends Function  {

    constructor(defaultValue){
        super("value", "return arguments.callee.apply(arguments);");
        this.value = defaultValue;
        this.defaultValue = defaultValue;
        this.changeListeners = [];
    }

    apply([value]){
        if(value!==undefined){
           if(value!==this.value){
               var oldValue = this.value;
               this.value=value;
               this.changeListeners.every((changeListener)=>changeListener(oldValue, value));
           }
        }
        return this.value;
    }

    clear(){
        this.value=undefined;
    }

    reset(){
        this.value=this.defaultValue;
    }

    addChangeListener(listener){
        this.changeListeners.push(listener);
    }

    removeChangeListener(listener){
        this.changeListeners.remove(listener);
    }

    clearChangeListeners(){
        this.changeListeners = [];
    }
}

Example usage:

import Attribute from './attribute.js';

var name= new Attribute();
name('foo'); //set value of name to 'foo'

name.addChangeListener((oldValue, newValue)=>{
    alert('value changed from ' +oldValue+ ' to ' +newValue);
});
alert(name()); //show value of name: 'foo'

name('baa'); //set value of name to new value 'baa' and trigger change listener
Stefan
  • 10,010
  • 7
  • 61
  • 117