ES6 allows to extend special objects. So it's possible to inherit from the function. Such object can be called as a function, but how can I implement the logic for such call?
class Smth extends Function {
constructor (x) {
// What should be done here
super();
}
}
(new Smth(256))() // to get 256 at this call?
Any method of class gets reference to the class instance via this
. But when it is called as a function, this
refers to window
. How can I get the reference to the class instance when it is called as a function?
The
super
call will invoke theFunction
constructor, which expects a code string. If you want to access your instance data, you could just hardcode it:but that's not really satisfying. We want to use a closure.
Having the returned function be a closure that can access your instance variables is possible, but not easy. The good thing is that you don't have to call
super
if you don't want to - you still canreturn
arbitrary objects from your ES6 class constructors. In this case, we'd doBut we can do even better, and abstract this thing out of
Smth
:Admittedly, this creates an additional level of indirection in the inheritance chain, but that's not necessarily a bad thing (you can extend it instead of the native
Function
). If you want to avoid it, usebut notice that
Smth
will not dynamically inherit staticFunction
properties.This is my approach to creating callable objects that correctly reference their object members, and maintain correct inheritance, without messing with prototypes.
Simply:
Extend this class and add a
__call__
method, more below...An explanation in code and comments:
View on repl.it
Further explanation of
bind
:function.bind()
works much likefunction.call()
, and they share a similar method signature:fn.call(this, arg1, arg2, arg3, ...);
more on mdnfn.bind(this, arg1, arg2, arg3, ...);
more on mdnIn both the first argument redefines the
this
context inside the function. Additional arguments can also be bound to a value. But wherecall
immediately calls the function with the bound values,bind
returns an "exotic" function object that transparently wraps the original, withthis
and any arguments preset.So when you define a function then
bind
some of its arguments:You call the bound function with only the remaining arguments, its context is preset, in this case to
['hello']
.This is the solution I've worked out that serves all my needs of extending functions and has served me quite well. The benefits of this technique are:
ExtensibleFunction
, the code is idiomatic of extending any ES6 class (no, mucking about with pretend constructors or proxies).instanceof
/.constructor
return the expected values..bind()
.apply()
and.call()
all function as expected. This is done by overriding these methods to alter the context of the "inner" function as opposed to theExtensibleFunction
(or it's subclass') instance..bind()
returns a new instance of the functions constructor (be itExtensibleFunction
or a subclass). It usesObject.assign()
to ensure the properties stored on the bound function are consistent with those of the originating function.Symbol
, which can be obfuscated by modules or an IIFE (or any other common technique of privatizing references).And without further ado, the code:
Edit
Since I was in the mood, I figured I'd publish a package for this on npm.
Firstly I came to solution with
arguments.callee
, but it was awful.I expected it to break in global strict mode, but seems like it works even there.
It was a bad way because of using
arguments.callee
, passing the code as a string and forcing its execution in non-strict mode. But than idea to overrideapply
appeared.And the test, showing I'm able to run this as function in different ways:
Version with
in fact contains
bind
functionality:Version with
makes
call
andapply
onwindow
inconsistent:so the check should be moved into
apply
:There is a simple solution which takes advantage of JavaScript's functional capabilities: Pass the "logic" as a function-argument to the constructor of your class, assign the methods of that class to that function, then return that function from the constructor as the result:
The above was tested on Node.js 8.
A shortcoming of the example above is it does not support methods inherited from the superclass-chain. To support that, simply replace "Object . getOwnPropertyNames(...)" with something that returns also the names of inherited methods. How to do that I believe is explained in some other question-answer on Stack Overflow :-). BTW. It would be nice if ES7 added a method to produce inherited methods' names as well ;-).
If you need to support inherited methods one possibility is adding a static method to the above class which returns all inherited and local method names. Then call that from the constructor. If you then extend that class Funk, you get that static method inherited along as well.
Update:
Unfortunately this doesn't quite work because it's now returning a function object instead of a class, so it seems this actually can't be done without modifying the prototype. Lame.
Basically the problem is there is no way of setting the
this
value for theFunction
constructor. The only way to really do this would be to use the.bind
method afterwards, however this is not very Class-friendly.We could do this in a helper base class, however
this
does does not become available until after the initialsuper
call, so it's a bit tricky.Working Example:
(Example requires modern browser or
node --harmony
.)Basically the base function
ClassFunction
extends will wrap theFunction
constructor call with a custom function which is similar to.bind
, but allows binding later, on the first call. Then in theClassFunction
constructor itself, it calls the returned function fromsuper
which is now the bound function, passingthis
to finish setting up the custom bind function.This is all quite a bit complicated, but it does avoid mutating the prototype, which is considered bad-form for optimization reasons and can generate warnings in browser consoles.