How do I inherit javascript functions ?
I hope that I'm understanding this right.
I believe you want a functor that's both an instance of a predefined prototype (yes, a class, just not a classic class) as well as directly callable? Right? If so, then this makes perfect sense and is very powerful and flexible (especially in a highly asynchronous environment like JavaScript). Sadly there is no way to do it elegantly in JavaScript without manipulating __proto__
. You can do it by factoring out an anonymous function and copying all of the references to all of the methods (which seems to be the direction you were heading) to act as a proxy class. The downsides to this are...
- It's very costly in terms of runtime.
(functorObj instanceof MyClass)
will never betrue
.- Properties will not be directly accessible (if they were all assigned by reference this would be a different story, but primitives are assigned by value). This can be solved with accessors via
defineProperty
or simply named accessor methods if necessary (it appears that that is what you're looking for, just add all properties to the functor withdefineProperty
via getters/setters instead of just functions if you don't need cross-engine support/backwards compatability). - You're likely to run into edge cases where final native prototypes (like Object.prototype or Array.prototype [if you're inheriting that]) may not function as expected.
- Calling
functorObj(someArg)
will always make thethis
context be the object, regardless of if it's calledfunctorObj.call(someOtherObj, someArg)
(this is not the case for method calls though) - Because the functor object is created at request time, it will be locked in time and manipulating the initial prototype will not affect the allocated functor objects like a normal object would be affected (modifying MyClass.prototype will not affect any functor objects and the reverse is true as well).
If you use it gently though, none of this should be a big deal.
In your prototype of your class define something like...
// This is you're emulated "overloaded" call() operator.
MyClass.prototype.execute = function() {
alert('I have been called like a function but have (semi-)proper access to this!');
};
MyClass.prototype.asFunctor = function(/* templateFunction */) {
if ((typeof arguments[0] !== 'function') && (typeof this.execute !== 'function'))
throw new TypeError('You really should define the calling operator for a functor shouldn\'t you?');
// This is both the resulting functor proxy object as well as the proxy call function
var res = function() {
var ret;
if (res.templateFunction !== null)
// the this context here could be res.asObject, or res, or whatever your goal is here
ret = res.templateFunction.call(this, arguments);
if (typeof res.asObject.execute === 'function')
ret = res.asObject.execute.apply(res.asObject, arguments);
return ret;
};
res.asObject = this;
res.templateFunction = (typeof arguments[0] === 'function') ? arguments[0] : null;
for (var k in this) {
if (typeof this[k] === 'function') {
res[k] = (function(reference) {
var m = function() {
return m.proxyReference.apply((this === res) ? res.asObject : this, arguments);
};
m.proxyReference = reference;
return m;
})(this.asObject[k]);
}
}
return res;
};
Resulting usage would look something like...
var aobj = new MyClass();
var afunctor = aobj.asFunctor();
aobj.someMethodOfMine(); // << works
afunctor.someMethodOfMine(); // << works exactly like the previous call (including the this context).
afunctor('hello'); // << works by calling aobj.execute('hello');
(aobj instanceof MyClass) // << true
(afunctor instanceof MyClass) // << false
(afunctor.asObject === aobj) // << true
// to bind with a previous function...
var afunctor = (new MyClass()).asFunctor(function() { alert('I am the original call'); });
afunctor() // << first calls the original, then execute();
// To simply wrap a previous function, don't define execute() in the prototype.
You could even chain bind countless other objects/functions/etc until the cows came home. Just refactor the proxy call a bit.
Hope that helps. Oh, and of course you could change the factory flow so that a constructor called without the new
operator then instantiates a new object and returns the functor object. However you prefer (you could surely do it other ways too).
Finally, to have any function become the execution operator for a functor in a bit more elegant of a manner, just make the proxy function a method of Function.prototype
and pass it the object to wrap if you want to do something like (you would have to swap templateFunction
with this
and this
with the argument of course)...
var functor = (function() { /* something */ }).asFunctor(aobj);
Raynos
Links: email: raynos2 AT gmail DOT com Github CV Github CV Work for Pie Klout G+ LinkedIn Twitter
Updated on August 02, 2022Comments
-
Raynos almost 2 years
// Don't break the function prototype. // pd - https://github.com/Raynos/pd var proto = Object.create(Function.prototype, pd({ "prop": 42 })); var f = function() { return "is a function"; }; f.__proto__ = proto; console.log(f.hasOwnProperty("prop")); // false console.log(f.prop); // 42 console.log(f()); // "is a function"
.__proto__
is non-standard and deprecated.How am I supposed to inherit prototypically creating an object but having that object be a function.
Object.create
returns an Object not a Function.new Constructor
returns an Object not a Function.Motivation: - A cross-browser
finherit
var finherit = function (parent, child) { var f = function() { parent.apply(this, arguments); child.apply(this, arguments); }; f.__proto__ = parent; Object.keys(child).forEach(function _copy(key) { f[key] = child[key]; }); return f; };
I don't believe this is possible, so we should probably propose a
Function.create
to the es-discuss mailing list/* Creates a new function whose prototype is proto. The function body is the same as the function fbody. The hash of propertydescriptors props is passed to defineproperties just like Object.create does. */ Function.create = (function() { var functionBody = function _getFunctionBody(f) { return f.toString().replace(/.+\{/, "").replace(/\}$/, ""); }; var letters = "abcdefghijklmnopqrstuvwxyz".split(""); return function _create(proto, fbody, props) { var parameters = letters.slice(0, fbody.length); parameters.push(functionBody(fbody)); var f = Function.apply(this, parameters); f.__proto__ = proto; Object.defineProperties(f, props); return f; }; })();
As mentioned in the es-discuss thread there exists a ES:strawman
<|
prototype operator which would allow for this.Let's see what it would look like using
<|
var f1 = function () { console.log("do things"); }; f1.method = function() { return 42; }; var f2 = f1 <| function () { super(); console.log("do more things"); } console.log(f1.isPrototypeOf(f2)); // true console.log(f2()); // do things do more things console.log(f2.hasOwnProperty("method")); // false console.log(f2.method()); // 42
-
Ben over 12 yearsCool. I'd never heard of functors. I'm definitely gonna name my next dog functor.
-
mr.stobbe over 12 yearsThis is really a pseudo functor (yes, funny name) proxying everything. It's a sloppy way to make it happen. Some languages support this natively. If EMCAScript5 allowed
__proto__
manipulation (or an equivalent alternative standard), or operator overloads, so would JavaScript. Sadly it doesn't. For all it's power and flexibility... sigh. -
Raynos over 12 years@mr.stobbe could you have a stab at using ES6 proxies to do this instead? (Works in chrome & FF). Alternatively you could cheat and pretend
<|
exists (protoOf = function (proto, thing) { thing.__proto__ = proto; }
) -
mr.stobbe over 12 years@Raynos - Yes. In fact quite a bit of this could have been written to support ES5 features but I purposely avoided them to show how it can be done in a completely backwards compatibly way (this would likely be supported by all current and recently outmoded engines). It was just to show that it can be done. The frustration of the question here was really around what's become a gap between ES specs so I wanted to provide a solution that would work everywhere.
-
Raynos over 12 years@mr.stobbe The main frustration is that ES5 can't do this and
.__protot__
is a non-standard deprecated hack that's not part of ES. You can't do it with ES5 features -
mr.stobbe over 12 years@Raynos Yes. Sorry... should have said "between implementations". Sorry, it was late.