nolo said:
I've recently begun playing with prototype.js as well as
scriptaculous, and found both very helpful in developing
web pages with lots of pop. Modal boxes, SlideUp/Down,
calendars, etc.
I've also recently ramped up my lurking and participation
in this newsgroup. There are some who advocate using prototype,
Here?
and others who are adamantly opposed to it. Are there any
concrete reasons why I should NOT use it?
Lots, but I don't have time to go through all of them right now.
However, one good illustration is the least you deserve.
Take this code form the latest (1.6.0) version of Prototype.js:-
| var Hash = Class.create(Enumerable, (function() {
| if (function() {
| var i = 0, Test = function(value) { this.key = value };
| Test.prototype.key = 'foo';
| for (var property in new Test('bar')) i++;
| return i > 1;
| }()) {
| function each(iterator) {
| var cache = [];
| for (var key in this._object) {
| var value = this._object[key];
| if (cache.include(key)) continue;
| cache.push(key);
| var pair = [key, value];
| pair.key = key;
| pair.value = value;
| iterator(pair);
| }
| }
| } else {
| function each(iterator) {
| for (var key in this._object) {
| var value = this._object[key], pair = [key, value];
| pair.key = key;
| pair.value = value;
| iterator(pair);
| }
| }
| }
| ...
- What this appears to represent is the conditional creation of a
function based upon (rather complex) test. But in ECMAScript terms this
is actually one big syntax error, because inside each of the
BlockStatement branches of the - if-else - there are what appear to be
function declarations, and BlockStatemen may only contain Statements
(not FunctionDeclarations (FunctionDeclarations and Statements being the
two distinct syntactic units from which ECMAScript Programs and function
bodies are constructed)).
Obviously if the 3 or 4 browsers with which Prototype.js 'works'
actually generated a syntax error that fact would have been noticed, so
what is happening? The first part of the answer is that JavaScript(tm)
has a non-standard syntax extension that is a FunctionStatement, which
(being a Statement) may appear inside a BlockStatement and can be
conditionally evaluated. ECMAScript allows for such extensions, though
they usually must be avoided when creating cross-browser code. The
second part of the answer is that JScript (and many other ECMAScript
implementations, including Opera's) are tolerant enough to
unconditionally treat these out of context 'FunctionDeclarations' as
FunctionDeclarations. But because FunctionDeclarations are processed
during variable instantiation, before any actual code execution, in
these environments the effect of the above is the equivalent of:-
var Hash = Class.create(Enumerable, (function() {
function each(iterator) {
for (var key in this._object) {
var value = this._object[key], pair = [key, value];
pair.key = key;
pair.value = value;
iterator(pair);
}
}
function each(iterator) {
var cache = [];
for (var key in this._object) {
var value = this._object[key];
if (cache.include(key)) continue;
cache.push(key);
var pair = [key, value];
pair.key = key;
pair.value = value;
iterator(pair);
}
}
if (function() {
var i = 0, Test = function(value) { this.key = value };
Test.prototype.key = 'foo';
for (var property in new Test('bar')) i++;
return i > 1;
}()) {
} else {
}
...
- Where the first - each - FunctionDeclaration results in the creation
of a function object and the assignment of a reference to that function
to the 'each' property of the variable object, and then the second -
each - FunctionDeclaration - results in the creation of a second
function object which is then assigned to the 'each' property of the
variable object, replacing the first. So in these environments it is the
second FunctionDeclaration that defines 'each', unconditionally, and
the - if-else - statement is effectively empty, making its execution
worthless.
Now if this code 'works' at all it 'works' mostly by coincidence as it
is relying on a non-standard extension wherever the - if-else - is
effective, and where the FunctionDeclaration are unconditionally acted
upon the result must only 'work' due to the order in which the two
declarations appear.
Of course the conditional creation of function objects is trivial in
javascript in a way that fully conforms with the ECMAScript
specification, and so in a way that can be expected to be fully
cross-browser and consistently executed in ECMA 262 3rd Ed. implementing
environments. Such code would be something like:-
var Hash = Class.create(Enumerable, (function() {
var each;
if (function() {
var i = 0, Test = function(value) { this.key = value };
Test.prototype.key = 'foo';
for (var property in new Test('bar')) i++;
return i > 1;
}()) {
each = function (iterator) {
var cache = [];
for (var key in this._object) {
var value = this._object[key];
if (cache.include(key)) continue;
cache.push(key);
var pair = [key, value];
pair.key = key;
pair.value = value;
iterator(pair);
}
}
} else {
each = function (iterator) {
for (var key in this._object) {
var value = this._object[key], pair = [key, value];
pair.key = key;
pair.value = value;
iterator(pair);
}
}
}
...
- replacing the 'syntax error' FunctionDeclaration with Expression
Statements where a Function Expression is assigned to an Identifier. The
result is fully ECMAScript standard and so will 'work' consistently in
all past, present and future ECMA 262 3rd Ed. conforming
implementations.
It is simply the case that anyone knowing javascript would have written
the original code in this final form. So would not be writing code that
functioned purely by coincidence but instead they would be programming.
The problem with the way this sort of ignorance of javascript results in
code that only works by coincidence is highlighted by browsers like
Opera. Opera currently acts like JScript, and unconditionally acts on
the FunctionDeclarations out of their (otherwise syntax error) context
but while Opera has an ECMA 262 conforming javascript implementation,
they also aim to emulate JavaScript(tm), and so may introduce
JavaScript(tm)'s FunctionStatement extension at any moment. And so code
that works by coincidence because of the order of the
'FunctionDeclarations' may find itself suddenly conditionally evaluating
FunctionStatements with any next version of Opera, and what these
branches do in that versions of Opera may be totally inappropriate.
The Prototype.js approach results in code that cannot be expected to
work in any more browsers than those in which it has been demonstrated
to 'work', and so cannot even be expected to 'work' with the future
versions of that those browsers. This cannot be a viable approach to
creating cross-browser code, and it does not.
That is quite a bit of writing in order to demonstrate that the authors
of Prototype.js don't know javascript. Similar demonstrations are
possible for bad design, ridiculous inefficiencies, avoidable bad
practices and much else besides, but I don't have time right now so you
will have to look them up in the archive.
The code itself is about a quarter as efficient as it could be, but
certain aspects of the design promote its extremely inefficient use, so
examples you will see are often orders of magnitude worse than that.
Fast computers mitigate that problem, but you would find systems created
with Prototype.js become unacceptably sluggish at much lower levels of
complexity.
It seems that today's computers have sufficient processing
power to overcome most slowly implemented solutions in
javascript.
Maybe, but that doesn't stop my boss continually wanting the client-side
code our web applications to run faster. He maintains that they are
easier to sell when they are fast, and then that they are easier to sell
if they have ever more bells and whistles. Obviously those are
contradictory demands.
And the benefits of being able to inject such
a wide variety of effects into a page (to me) far
outweigh any harm.
At least for as long as you avoid understanding how those effects work,
and so how trivial they are to create for yourself.
Richard.