Cracking prototype.js

E

Elf M. Sternberg

One of the complaints about prototype.js (google for it if you're not
familiar with it) is that it's poorly documented. I have this inkling
that the key to understanding prototype.js is in the bind function.

The problem with Javascript is that the "this" operator is poorly
overloaded and it is often hard to understand in the context of
object-oriented javascript

So, let's start with the definition:

Function.prototype.bind = function(object) {
var method = this;
return function() {
method.apply(object, arguments);
}
}

As I read this, it states that all functions (which are themselves
objects) will, in the future, have an associated method called "bind".
The function() function, so to speak, simply instantiates a Function
object with the parameter list and then evals the statement, sticking
the resulting execution-tree in the current code frame.

The "this" there refers to the function object associated with the call
to bind(), right? But the word "arguments" there refers to the
arguments passed to the function object *generated by* the call to
bind().

In every example within prototype.js, bind() is called either in a
constructor or within a method contexted to a javascript object, and is
always called with "this" as its argument, e.g.:

this.options.onComplete = this.updateContent.bind(this);

As I read the code it seems to be stating that the "this" object
referred to within bind()'d functions are being coerced into always
referring to the current instantiated object.

But isn't this always the case anyway? Is this a rather confusing
attempt to ensure "this" purity whereby the call

method.apply(object, arguments)

is forced to always have the reference to the containing object present?

I think I've got it. Bind() generates uniq functions that contain live
references to the objects to which they belong, such that the function
object can then be passed to setTimeout() or onMouseOver(), handlers
that accept functions but not objects.

Have I got this right?

Elf
 
Y

Yann-Erwan Perio

Elf said:
One of the complaints about prototype.js (google for it if you're not
familiar with it) is that it's poorly documented. I have this inkling
that the key to understanding prototype.js is in the bind function.

I didn't know the file you refer too, at first sight it looks promising
and deserves more time - exciting moments for next Sunday:)
Function.prototype.bind = function(object) {
var method = this;
return function() {
method.apply(object, arguments);
}
}

An excellent example which should illustrate perfectly how javascript
handles prototypes and closures.
As I read this, it states that all functions (which are themselves
objects) will, in the future, have an associated method called "bind".

Indeed; this is permitted by javascript prototypes.

In javascript, functions can be executed in two manners:
- in the "constructor way", when called with the "new" keyword,
- in the "call way", when called without the "new" keyword.

When called as a constructor, a function executes the following steps
(roughly):
- create an empty object, which will be the instance, and which will be
referred to "this" in the function's body that's going to be executed,
- associate a prototype to this instance; normally the link to this
prototype is not enumerable on the instance (this is the Ecma
[[prototype]] property, made accessible in Mozilla under the "__proto__"
property"); the prototype associated is a simple object, which can be
defined by the programmer with the "prototype" property of the
*constructor* (this property not being the prototype of the constructor
itself, but truly the prototype to be associated to instances
constructed by the constructor),
- execute the function's body, as in a "call way".

Each function is, in javascript, an object, created by a regular
constructor called "Function". This means that each function has an
"internal link" __proto__ to a unique object, Function.prototype.

When we call foo.bind(), the identifier resolution will first look at
properties defined for the function "foo"; since it has no "bind"
property, it will look up the prototype and search the property on the
prototype; if the prototype does not possess the property, then the
identifier will be searched on the prototype's prototype, and so on...
until the ultimate prototype (null) is reached - this is the "prototype
chain". When the "bind" function is found, it is executed, and the
"this" value associated to the execution context is the function/object
"foo".

---
function Foo(){}
Foo.prototype.bar=42;

var f1=new Foo();
var f2=new Foo();

f1.bar=50;

alert(f1.bar); //50
alert(f2.bar); //42

Foo.prototype.bar=70;
alert(f1.bar); //50
alert(f2.bar); //70

delete f1.bar; //remove the property on f1
alert(f1.bar); //70
---

More information on prototypes can be found in the ECMAScript
specification ECMA262-3, §15.
The "this" there refers to the function object associated with the call
to bind(), right? But the word "arguments" there refers to the
arguments passed to the function object *generated by* the call to
bind().

That's right; the code you've provided nicely exploits the way functions
work in javascript:
- functions can be created in several ways, either using the Function
constructor, or using function expressions; the way a function is
created is important in regards of the scope it will have access to,
- when creating a function using a function expression/declaration, we
can nest functions inside functions; an inner function will have access
to the "container function" variables, even if the "container function"
has been executed and has returned (closures / lexical scoping),
- the "this" value available in a function depends on the way the
function is called; it is either the global object if the function is
called directly, or the instance to which the function is bound as a
method, if called as a method; the "this" value can even be set by the
programmer, using "apply" or "call".

More information on Function.prototype.apply can be found in the
ECMAScript specification, ECMA262-3, §15.3.4.3.

More information on the "this" value, "Arguments" object, and more
generally execution contexts, can be found in the ECMAScript
specification, ECMA262-3, §10.
In every example within prototype.js, bind() is called either in a
constructor or within a method contexted to a javascript object, and is
always called with "this" as its argument, e.g.:

this.options.onComplete = this.updateContent.bind(this);

As I read the code it seems to be stating that the "this" object
referred to within bind()'d functions are being coerced into always
referring to the current instantiated object.

But isn't this always the case anyway? Is this a rather confusing
attempt to ensure "this" purity whereby the call

method.apply(object, arguments)

is forced to always have the reference to the containing object present?

Well, if the author always passes "this" to the "bind" calls then indeed
there's no added value, the "binding" process is useless, brings some
confusion and even gets you a slight memory overhead.

However I can understand the will of the author to have something
generic enough, so that the function can be used with other "this"
values (it's possibly better to define the "this" value explicitly in
the call rather than calling the bind() function in a context with an
appropriate "this" value).
I think I've got it. Bind() generates uniq functions that contain live
references to the objects to which they belong, such that the function
object can then be passed to setTimeout() or onMouseOver(), handlers
that accept functions but not objects.

Have I got this right?

Definitely yes!

Reading the following should clear all your doubts:
<URL:http://www.jibbering.com/faq/faq_notes/closures.html>


Regards,
Yep.
 
E

Elf M. Sternberg

Yann-Erwan Perio said:
I didn't know the file you refer too, at first sight it looks
promising and deserves more time - exciting moments for next Sunday:)

Thank you so much for the confirmation and links! Yes, I
stumbled upon prototype.js a couple of weeks ago; it's an
language-agnostic version of the toolkit used for Ruby On Rails, but it
is not at all document, and I've been struggling to understand it. If
you think *this* is promising, you should see what they do with it:

http://prototype.conio.net/

And there are mind-boggling special visual effects achieved with
it:

http://mir.aculo.us/effects/index.html

Both are under a very generous license. (Hence my effort to
understand it; I'd like to understand it enough to both use it and
contribute back documentation.)

Elf
 
V

VK

I guess the inspiration idea of this library was to overcome "this"
discrepancy in IE.

Consider this intrinsic event capturer:

(1)
....
<div id="div1" onclick="someFunctionFor(this)">
....
Here "this" will point nicely to the event source (div1), so we can
forward it to the event handler someFunctionFor().

I couldn't find an official term for a code located within <script>
tags as opposed to intrinsic code within an element tag, so lets us
call it "scriptboded code" (you are very welcome for a better
term).
So consider now this scriptboded code:

(2)
....
someFunctionFor(e) {
/* In IE "this" points now to the current window!
Luckily we still have event.srcElement property
to determine the actual event capturer (div1)
*/
}

document.getElementById('div1').onclick = someFunctionFor;
....

But consider this HTML code:
<div id="div1">DIV 1
<span id="span1">SPAN 1
<a href="foo.html">LINK</a>
</span>
</div>

In this situation div1 will receive any events not only for itself, but
also for all underlying elements (span & a).
event.srcElement in (2) is out of use, because it will point to the
most underlying element for the event bubble (it can be div, span or a
depending on the actual place you clicked/pressed etc.)

The obvious solution would be to use function wrapper (closure in
JavaScript term):
document.getElementById('div1').onclick =
function(){someFunctionFor(this);};

This brings us though to the infamous circular reference memory leak
bug in IE:
<http://support.microsoft.com/default.aspx?scid=kb;EN-US;830555>

I'm sorry for being so multiworded but the situation requires all
these preliminary explanations. I see that "this" discrepancy is too
"rude" and obvious to be a bug. It rather seems like "this"
functionality was intentionally half-killed in IE to escape that memory
leak. It might be too deepcoded into the program body to be easily
fixed.

I'm afraid that prototype.js is really a further development of
document.getElementById('div1').onclick =
function(){someFunctionFor(this);};

It's still a closure but moved on the prototype level which makes it
even more dangerous for memory management.

I cannot tell for sure without profound test, but prototype.js on IE
will be a source of a huge amount of uncollectible trash (means objects
cannot be removed by gc).
 
V

VK

A further confirmation of my guess could be found in the fact that IE
event model has fromElement and toElement properties but they are set
only for mouseout/mouseover events. Instead of rectify this (as well as
solving the "this" problem) they introduced in the latter releases
document.activeElement (global event capturer) and obj.setActive()
method. So in the case of nested elements they have proposed an option
to track targets using from/toElement and to set activeElement
accordingly.
This gives us two possible explanation:

1) MS programmers do not know how to programm
- Sorry, but *not* true, whatever you may think about MS (M$)

2) MS run into an evangelism problem (something lying into the very
core of their OS) and they really don't know how to pull out of this on
the browser level.
Evidently in the latter case no one JavaScript patch can help. (They
would make an .exe IE patch long time ago).
 
T

Thomas 'PointedEars' Lahn

Elf said:
One of the complaints about prototype.js (google for it if you're not
familiar with it) is that it's poorly documented.

It is not only poorly documented but most certainly based on a (common?)
misconception. What should an "object-oriented Javascript library" be?
J(ava)Script/ECMAScript *are* object-oriented programming languages;
that JavaScript < 2.0 (v2.0 is only available in the Epimetheus test
implementation), JScript < 7.0 (v7.0 in .NET framework only) and
ECMAScript < 4 (Ed. 4 is still only a Netscape proposal) only provide
for prototype-based inheritance does not diminish neither their status
nor that of other OOP languages using the same object model.

While emulating class-based inheritance in a prototype-based language
may be interesting, the approach taken here is (at least to me) somewhat
bogus. Instead of using proper function declarations for constructors,
an object is created using a previously created prototype with
Class.create(), which in turn requires an `initialize' property that
requires the Function.prototype.apply() method to be called, which is
modified later. I can see no purpose or advantage in that other than
code obfuscation (if this can be even called an advantage); it reduces
code efficiency and increases the average memory footprint compared to
calling a constructor function (which can do the initialization) with
the `new' keyword.


PointedEars
 
T

Thomas 'PointedEars' Lahn

commercial said:
I have never used "Class" or "initialize".

I did not state any of the kind.
Once you posted code instead of
"chopped liver academia"
you will have started to learn.

Once you posted a non-intended-to-be-insulting, meaningful response that
refers to the actual text written before, which shows you have read and
understand the newsgroup's charta and FAQ, you will have started to think.
Until then, you want to stay away from the input devices within your reach.
You have been warned.
[senseless reply including full top post quote snipped]


PointedEars
 
T

Thomas 'PointedEars' Lahn

commercial said:
I have never used "Class" or "initialize".

I did not state any of the kind.
Once you posted code instead of
"chopped liver academia"
you will have started to learn.

Once you posted a non-intended-to-be-insulting, meaningful response that
refers to the actual text written before, which showed you have read and
understood the newsgroup's charta and FAQ, you will have started to think.
Until then, you want to stay away from the input devices within your reach.
You have been warned.
[senseless reply including full top post quote snipped]


PointedEars
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top