Problem 1: ClojureScript functions as object methods
In any programming language with both first class functions and objects, the handling of a this
parameter is a cause for complication. In the case of JavaScript the picture is particularly sticky due to langauge foibles. An additional complication, ClojureScript does not have its own notion of this
independent to its JavaScript interop. Currently, there is no good way to refer to a JavaScript this
aside from the (js* "this")
hack. ClojureScript should more effectively handle this
in a way that is safe and intuitive.
A bit about this
in general
ClojureScript functions are compiled to JavaScript functions and are therefore subject to the implicit this
behavior.
Given a function f
defined as:
function f(a,b) { print([this,a,b]) }
Calling f
directly yields:
f(1,2) // [object global],1,2
note: in JavaScript strict mode this
would be bound to undefined
in the previous example
In this case, the this
variable is implicitly bound to the global object.
However, when bound as a property on an object, f
's implicit this
changes:
var o = {} o.m = f o.m(1,2) // [object Object],1,2
Now the value of this
is the instance o
.
ClojureScript and this
The problem in ClojureScript can be illustrated in the following:
(defn f [a b] (println [(js* "this") a b])) ;;roughly equivalent to the f function above (def o (js* "{}")) (set! cljs.user.o.m f) ((.m o) 1 2) ;; [#<[object Global]> 1 2]
That is, the value for this
in the body of f
is bound to the global object and not the instance o
.
A possible solution for ClojureScript functions as methods
One possible solution to this dilemma is to use GClosure's bind
function:
(set! cljs.user.o.m (goog.bind f o)) ((.m o) 1 2) ;; [#<[object Object]> 1 2]
This will also work for multi-arity functions.
A possible syntax
(extend-object o {:m f})
Would expand into a number of calls to goog.bind
on the object o
and then return o
itself. The use of the (js* "this")
could possibly be replaced with the js
namespaced reference js/this
.
Caveat (or bug, depending on how you look at it)
Any function attached to an object via extend-object
will forever thereafter refer to said object as its this
.
Problem 2: ClojureScript functions and co-opted this
Some JavaScript libraries use this
as a dynamic variable bound to some special value within special contexts. For example, JQuery uses this approach:
$('li').each(function(index) { alert(index + ': ' + $(this).text()); });
In this example, this
would be bound to each element in turn retrieved by the selector $('li')
and processed in the each
loop. techniques of this sort pull this off by explicitly passing a value for the implicit this
to the target function as an argument to the call
method. In other words, something (in spirit) like:
(defn f [] (.x js/this)) (f) ;=> undefined (.call f (js* "{x : 9}")) ;=> 9
This is effectively what goog.bind
does, so this type of behavior should just work using ClojureScript functions.
1 Comment
Hide/Show CommentsSep 07, 2011
Tom Hickey
In your example code, I believe
((.m o) 1 2)
should be changed to(. o (m 1 2))
. The former takes the value of the propertym
ofo
, a function, then calls it (akin to the js:var myfn = o.m; myfn(1 2);
which will havethis
bound to the global object). The latter callsm
as a method and works as intended.Some discussion on this from Rich here
Doesn't solve that there is not a good way to refer to
this
.edit: since it's not a 0-arity fn, it could be written with the more normal syntax of
(.m o 1 2)
, guess I didn't have enough coffee before posting