Firebug represents (new Array(N)) as an array with N undefineds in it. I recently ran across a scenario that demonstrated that a sized array with all undefined values in it is different from a newly constructed, sized array. I'd like to understand the difference.
Suppose you want to generate a list of random integers between 0 and 1000.
function kilorange() {
return Math.floor(Math.random() * (1001));
}
no_random_numbers = (new Array(6)).map(kilorange);
my_random_numbers = [undefined, undefined, undefined,
undefined, undefined, undefined].map(kilorange);
I would have expected no_random_numbers and my_random_numbers to be equivalent, but they're not. no_random_numbers is another array of undefineds, whereas my_random_numbers is an array with six random integers in it. Furthermore, after throwing a console.count statement into kilorange, I learned that my function never gets called for the array created with the Array constructor.
What is the difference, and why does map (and presumably other iterable methods) not treat the above arrays the same?
The ES standard (15.4.4.19) defines the algorithm for map, and it's quite clear from step 8b that since your array doesn't actually have any of those elements, it will return an "empty" array with length 6.
As others have mentioned, it has to do with array objects in js being (unlike their rigid C counterparts) very dynamic, and potentially sparse (see 15.4 for the sparsity test algorithm).
When you use:
var a = new Array(N);
no values are stored in the new array and even the index "properties" are not created. That is why map won't do a thing on that array.
The fact that Firebug does that is a bug/feature of the Firebug. You should remember that it's console is an eval envelope. There are other bug/features in the Firebug console.
For example in Chrome console you'll see the above a array as [].
Look at this sample and run it: http://jsfiddle.net/ArtPD/1/ (it create the two arrays without using the map over them and then list the key/values of each one of them)
I would say that (new Array(6)) doesn't allocate "named" properties (so it doesn't create "1": undefined, "2": undefined...) while the other form [undefined, ... ] does.
In fact if I use a for (var i in ... ) the two outputs are:
no_random_numbers
my_random_numbers
0 undefined
1 undefined
2 undefined
3 undefined
4 undefined
5 undefined
Good question, good answers. I fiddled a bit with the map prototype from MDN. If it's adapted like this, map would work for a new Array([length])
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
var O = Object(this);
var len = O.length >>> 0;
if ({}.toString.call(callback) != "[object Function]") {
throw new TypeError(callback + " is not a function");
}
if (thisArg) {
T = thisArg;
}
A = new Array(len);
k = 0;
while(k < len) {
var kValue, mappedValue;
if (k in O || (O.length && !O[k])) {
// ^ added this
kValue = O[ k ];
mappedValue = callback.call(T, kValue, k, O);
A[ k ] = mappedValue;
}
k++;
}
return A;
};
Based on Casy Hopes answer you could also make a mapx-extension to be able to use map with some new Array([length]):
Array.prototype.mapx = function(callback){
return this.join(',').split(',').map(callback);
}
//usage
var no_random_numbers = new Array(6).mapx(kilorange);
To answer the title question (why presize arrays), the only use that I've come across for initializing an Array(n) array is to create an n-1 length string:
var x = Array(6).join('-'); // "-----"
To answer why you would presize an array, you'd do it so you don't have to do as many individual allocations. Allocating memory takes some time, and it might trigger a garbage collection run that can take a lot longer.
On a typical web page, you won't notice a difference, but if you're doing something major, it might help a fair bit.
Related
As someone used to python and C++, having an = copy objects by reference rather than value is not intuitive at all. Not just that, but there seems to be no direct way of copying objects to begin with. JSON.parse(JSON.stringify) is the closest option (if I know correctly), and even that has problems.
a) In a language where all variables are anyway treated as objects, why does the = operator distinguish between primitive and non-primitive data types to decide whether to copy by value or reference?
b) Why is copy by value not possible for objects?
c) What techniques are helpful for a beginner used to copying objects by value to code without it?
a) In a language where all variables are anyway treated as objects,
why does the = operator distinguish [...] ?
The =(assign) operator does not distinguish between primitive and non primitive data types. It kinda does the same for both, considering that equality is preserved after assignment (excluding exceptions e.g. NaN, ...).
b) Why is copy by value not possible for objects?
Wrong assumption in a) leads to this. Assignment is no copy and a copy of an object does not preserve equality.
Or think about:
var obj = {a: {b: 1}}
.
What is the value of obj.a ? It is just the reference to {b:1}.
c) What techniques are helpful for a beginner used to copying objects
by value to code without it?
There are many approaches for this. And two trivial cases.
As the first case one knows the layout of the object. Thus creates a template or constructor and passes all values into the corresponding properties.
As the second case one assumes a cyclic object containing everything possible in javascript (functions, regexp, symbols, undefined, ...) of depth n and builds something (not json.stringify).
For starters: possible duplicate
Assumptions:
primitive and non primitive data types have default getter,setter, ...
I guess it's because of the specific nature of JS. When you create an object like this:
let obj = {a: 3, b: 5}
And try to pass this object to another variable like this:
let obj2 = obj
You will still have only 1 object, but with 2 references, so if you try to modify obj.a it will also affect obj2.a.
That's why I created a way around for myself, which may not be the best, but here is it:
//A little helper
Object.isObject = function (object) {
return object !== null && object instanceof Object && !Array.isArray(object)
}
/*
* Array and Object copy work with each other, so every nested array are
* copied properly
*/
/*
* ARRAY
*/
Object.defineProperty(Array.prototype, 'copy', {
value: function (array){
if(!(Array.isArray(array))) throw new TypeError('passed value should be an instance of array')
if(array.length <= 0) {
console.warn('WARNING: Found nothing to copy')
return this
}
this.splice(0, this.length)
for(let i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) this[i] = Array().copy(array[i])
else if (Object.isObject(array[i])) this[i] = Object().copy(array[i])
else this[i] = array[i]
}
return this
},
enumerable: false
})
/*
* OBJECT
*/
Object.defineProperty(Object.prototype, 'copy', {
value: function (object){
if(!object || !(Object.isObject(object))) return false
if(Object.entries(object) <= 0) {
console.warn('WARNING: Found nothing to copy')
return this
}
const props = Object.getOwnPropertyNames(this)
for (let i = 0; i < props.length; i++) delete this[props[i]]
const keys = Object.keys(object)
const values = Object.values(object)
for (let i = 0; i < keys.length; i++) {
if(Array.isArray(values[i])) this[keys[i]] = Array().copy(values[i])
else if(Object.isObject([values[i]])) this[keys[i]] = Object().copy(values[i])
else this[keys[i]] = values[i]
}
return this
},
enumerable: false
})
//create 2 arrays
let a = [3, 5, {a: 5}, [3, 1]]
let b = []
//copy array of a
b.copy(a)
//modify values
b[0] = 6
b[2].a = 1
b[3][0] = 'test'
console.log(a) //source
console.log(b)
As you can see in the example these 2 arrays (a and b) are completely different and have no relation to each other.
P.S. Sorry if I wrote something wrong, my english is not that good :O
First, let me define what is short-cut fusion for those of you who don't know. Consider the following array transformation in JavaScript:
var a = [1,2,3,4,5].map(square).map(increment);
console.log(a);
function square(x) {
return x * x;
}
function increment(x) {
return x + 1;
}
Here we have an array, [1,2,3,4,5], whose elements are first squared, [1,4,9,16,25], and then incremented [2,5,10,17,26]. Hence, although we don't need the intermediate array [1,4,9,16,25], we still create it.
Short-cut fusion is an optimization technique which can get rid of intermediate data structures by merging some functions calls into one. For example, short-cut fusion can be applied to the above code to produce:
var a = [1,2,3,4,5].map(compose(square, increment));
console.log(a);
function square(x) {
return x * x;
}
function increment(x) {
return x + 1;
}
function compose(g, f) {
return function (x) {
return f(g(x));
};
}
As you can see, the two separate map calls have been fused into a single map call by composing the square and increment functions. Hence the intermediate array is not created.
Now, I understand that libraries like Immutable.js and Lazy.js emulate lazy evaluation in JavaScript. Lazy evaluation means that results are only computed when required.
For example, consider the above code. Although we square and increment each element of the array, yet we may not need all the results.
Suppose we only want the first 3 results. Using Immutable.js or Lazy.js we can get the first 3 results, [2,5,10], without calculating the last 2 results, [17,26], because they are not needed.
However, lazy evaluation just delays the calculation of results until required. It does not remove intermediate data structures by fusing functions.
To make this point clear, consider the following code which emulates lazy evaluation:
var List = defclass({
constructor: function (head, tail) {
if (typeof head !== "function" || head.length > 0)
Object.defineProperty(this, "head", { value: head });
else Object.defineProperty(this, "head", { get: head });
if (typeof tail !== "function" || tail.length > 0)
Object.defineProperty(this, "tail", { value: tail });
else Object.defineProperty(this, "tail", { get: tail });
},
map: function (f) {
var l = this;
if (l === nil) return nil;
return cons(function () {
return f(l.head);
}, function () {
return l.tail.map(f);
});
},
take: function (n) {
var l = this;
if (l === nil || n === 0) return nil;
return cons(function () {
return l.head;
}, function () {
return l.tail.take(n - 1);
});
},
mapSeq: function (f) {
var l = this;
if (l === nil) return nil;
return cons(f(l.head), l.tail.mapSeq(f));
}
});
var nil = Object.create(List.prototype);
list([1,2,3,4,5])
.map(trace(square))
.map(trace(increment))
.take(3)
.mapSeq(log);
function cons(head, tail) {
return new List(head, tail);
}
function list(a) {
return toList(a, a.length, 0);
}
function toList(a, length, i) {
if (i >= length) return nil;
return cons(a[i], function () {
return toList(a, length, i + 1);
});
}
function square(x) {
return x * x;
}
function increment(x) {
return x + 1;
}
function log(a) {
console.log(a);
}
function trace(f) {
return function () {
var result = f.apply(this, arguments);
console.log(f.name, JSON.stringify([...arguments]), result);
return result;
};
}
function defclass(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
As you can see, the function calls are interleaved and only the first three elements of the array are processed, proving that the results are indeed computed lazily:
square [1] 1
increment [1] 2
2
square [2] 4
increment [4] 5
5
square [3] 9
increment [9] 10
10
If lazy evaluation is not used then the result would be:
square [1] 1
square [2] 4
square [3] 9
square [4] 16
square [5] 25
increment [1] 2
increment [4] 5
increment [9] 10
increment [16] 17
increment [25] 26
2
5
10
However, if you see the source code then each function list, map, take and mapSeq returns an intermediate List data structure. No short-cut fusion is performed.
This brings me to my main question: do libraries like Immutable.js and Lazy.js perform short-cut fusion?
The reason I ask is because according to the documentation, they “apparently” do. However, I am skeptical. I have my doubts whether they actually perform short-cut fusion.
For example, this is taken from the README.md file of Immutable.js:
Immutable also provides a lazy Seq, allowing efficient chaining of collection methods like map and filter without creating intermediate representations. Create some Seq with Range and Repeat.
So the developers of Immutable.js claim that their Seq data structure allows efficient chaining of collection methods like map and filter without creating intermediate representations (i.e. they perform short-cut fusion).
However, I don't see them doing so in their code anywhere. Perhaps I can't find it because they are using ES6 and my eyes aren't all too familiar with ES6 syntax.
Furthermore, in their documentation for Lazy Seq they mention:
Seq describes a lazy operation, allowing them to efficiently chain use of all the Iterable methods (such as map and filter).
Seq is immutable — Once a Seq is created, it cannot be changed, appended to, rearranged or otherwise modified. Instead, any mutative method called on a Seq will return a new Seq.
Seq is lazy — Seq does as little work as necessary to respond to any method call.
So it is established that Seq is indeed lazy. However, there are no examples to show that intermediate representations are indeed not created (which they claim to be doing).
Moving on to Lazy.js we have the same situation. Thankfully, Daniel Tao wrote a blog post on how Lazy.js works, in which he mentions that at its heart Lazy.js simply does function composition. He gives the following example:
Lazy.range(1, 1000)
.map(square)
.filter(multipleOf3)
.take(10)
.each(log);
function square(x) {
return x * x;
}
function multipleOf3(x) {
return x % 3 === 0;
}
function log(a) {
console.log(a);
}
<script src="https://rawgit.com/dtao/lazy.js/master/lazy.min.js"></script>
Here the map, filter and take functions produce intermediate MappedSequence, FilteredSequence and TakeSequence objects. These Sequence objects are essentially iterators, which eliminate the need of intermediate arrays.
However, from what I understand, there is still no short-cut fusion taking place. The intermediate array structures are simply replaced with intermediate Sequence structures which are not fused.
I could be wrong, but I believe that expressions like Lazy(array).map(f).map(g) produce two separate MappedSequence objects in which the first MappedSequence object feeds its values to the second one, instead of the second one replacing the first one by doing the job of both (via function composition).
TLDR: Do Immutable.js and Lazy.js indeed perform short-cut fusion? As far as I know they get rid of intermediate arrays by emulating lazy evaluation via sequence objects (i.e. iterators). However, I believe that these iterators are chained: one iterator feeding its values lazily to the next. They are not merged into a single iterator. Hence they do not “eliminate intermediate representations“. They only transform arrays into constant space sequence objects.
I'm the author of Immutable.js (and a fan of Lazy.js).
Does Lazy.js and Immutable.js's Seq use short-cut fusion? No, not exactly. But they do remove intermediate representation of operation results.
Short-cut fusion is a code compilation/transpilation technique. Your example is a good one:
var a = [1,2,3,4,5].map(square).map(increment);
Transpiled:
var a = [1,2,3,4,5].map(compose(square, increment));
Lazy.js and Immutable.js are not transpilers and will not re-write code. They are runtime libraries. So instead of short-cut fusion (a compiler technique) they use iterable composition (a runtime technique).
You answer this in your TLDR:
As far as I know they get rid of intermediate arrays by emulating lazy
evaluation via sequence objects (i.e. iterators). However, I believe
that these iterators are chained: one iterator feeding its values
lazily to the next. They are not merged into a single iterator. Hence
they do not "eliminate intermediate representations". They only
transform arrays into constant space sequence objects.
That is exactly right.
Let's unpack:
Arrays store intermediate results when chaining:
var a = [1,2,3,4,5];
var b = a.map(square); // b: [1,4,6,8,10] created in O(n)
var c = b.map(increment); // c: [2,5,7,9,11] created in O(n)
Short-cut fusion transpilation creates intermediate functions:
var a = [1,2,3,4,5];
var f = compose(square, increment); // f: Function created in O(1)
var c = a.map(f); // c: [2,5,7,9,11] created in O(n)
Iterable composition creates intermediate iterables:
var a = [1,2,3,4,5];
var i = lazyMap(a, square); // i: Iterable created in O(1)
var j = lazyMap(i, increment); // j: Iterable created in O(1)
var c = Array.from(j); // c: [2,5,7,9,11] created in O(n)
Note that using iterable composition, we have not created a store of intermediate results. When these libraries say they do not create intermediate representations - what they mean is exactly what is described in this example. No data structure is created holding the values [1,4,6,8,10].
However, of course some intermediate representation is made. Each "lazy" operation must return something. They return an iterable. Creating these is extremely cheap and not related to the size of the data being operated on. Note that in short-cut fusion transpilation, an intermediate representation is also made. The result of compose is a new function. Functional composition (hand-written or the result of a short-cut fusion compiler) is very related to Iterable composition.
The goal of removing intermediate representations is performance, especially regarding memory. Iterable composition is a powerful way to implement this and does not require the overhead that parsing and rewriting code of an optimizing compiler which would be out of place in a runtime library.
Appx:
This is what a simple implementation of lazyMap might look like:
function lazyMap(iterable, mapper) {
return {
"##iterator": function() {
var iterator = iterable["##iterator"]();
return {
next: function() {
var step = iterator.next();
return step.done ? step : { done: false, value: mapper(step.value) }
}
};
}
};
}
Consider:
var a = Array(3);
var b = [undefined,undefined,undefined];
What's the reason that a.map and b.map produce different results?
a.map(function(){ return 0; }); //produces -> [undefined,undefined,undefined]
b.map(function(){ return 0; }); //produces -> [0,0,0]
The array constructor creates an array with the given length. It does not create the keys. Array.prototype.map's callback function is only executed for the elements in the list.
That is, all values which are associated with a key (integer) 0 ≤ i < length.
Array(3) has zero keys, so .map's callback is never triggered.
[void 0, void 0, void 0] has three keys, for which the callback function is executed.
Array(3).hasOwnProperty(0); // false
[void 0, void 0, void 0].hasOwnProperty(0); // true
The specification and its polyfill are mentioned at MDN. At line 47, if (k in O) { shows that non-existant keys are not treated by the callback function.
From MDN:
callback is invoked only for indexes of the array which have assigned
values; it is not invoked for indexes which have been deleted or which
have never been assigned values.
For the array a, you've instantiated an array of length 3 but have not assigned any values. The map function finds no elements with assigned values, so it does not produce a new array.
For the array b, you've instantiated an array of 3 elements, each with the value undefined. The map function finds 3 elements with assigned values, and returns '0' as the new value for each of them in a new array.
map only iterates existing properties, not empty indices.
Therefore, if you want it to work, you must first fill the array.
There are multiple ways to do that, for example:
.fill(), introduced in ES6
console.log(new Array(3).fill().map(function(){ return 0; }));
Call concat with apply:
var arr = [].concat.apply([], new Array(3));
console.log(arr.map(function(){ return 0; }));
An old for loop.
var arr = new Array(3);
for(var i=0; i<arr.length; ++i) arr[i] = 1; /* whatever */
console.log(arr.map(function(){ return 0; }));
Use some idea from Most efficient way to create a zero filled JavaScript array?
Etcetera.
a is an empty array that doesn't have elements, so map function produces empty array without elements (per specification, map produces results only if [[HasProperty]] is true.) b is an array of three elements, so map produces an array of three elements.
Constructed arrays are enumerable but empty
Array(len) creates an array and sets its length accordingly but only its length is "enumerable", not the values contained. So, you cannot map the array Array(100).map(/* nope */)— it's not a "real array" yet and it is actually empty despite having the correct length.
callback is invoked only for indexes of the array which have assigned values including undefined.,
The array does not contain any values; not even undefined
It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).
To populate array you need to iterate it someway… E.g.: [...Array(100)] or Array.from(Array(100))
I imagine the purpose of this initialization is to optimize memory allocation… there isn't really anything in the array. MDN says "empty arrayLength objects" which might be misleading as trying to access any of the "empty items" just returns undefined… But we know they aren't really undefined since map fails, therefore we can confirm it is a truly empty array.
Constructor equivalent
This example does not seek to mirror specification but instead to illustrate why an array returned from Array cannot yet be iterated
function * (length) {
const arr = [];
Object.defineProperty(arr, 'length', { value: length });
// Equivalent, but invokes assignment trap and mutates array:
// arr.length = length;
Object.defineProperty(arr, Symbol.iterator, {
value() {
let i = 0;
return {
next() {
return {
value: undefined, // (Not omitted for illustration)
done: i++ == length
};
}
}
}
})
return arr;
}
It is worth pointing out that despite providing a undefined value in the generator value property, it does not recognize it as a value and so the array is empty.
Array(len) Specification
https://www.ecma-international.org/ecma-262/6.0/#sec-array-len
Array (len) This description applies if and only if the Array
constructor is called with exactly one argument.
1) Let numberOfArgs be the number of arguments passed to this function
call.
2) Assert: numberOfArgs = 1.
3) If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
4) Let proto be GetPrototypeFromConstructor(newTarget, "%ArrayPrototype%").
5) ReturnIfAbrupt(proto).
6) Let array be ArrayCreate(0, proto).
7) If Type(len) is not Number, then
a) Let defineStatus be CreateDataProperty(array, "0", len).
b) Assert: defineStatus is true.
c) Let intLen be 1.
8) Else, a) Let intLen be ToUint32(len). b) If intLen ≠ len, throw a RangeError exception.
9) Let
setStatus be Set(array, "length", intLen, true).
10) Assert: setStatus is not an abrupt completion.
11) Return array.
Due to object in javascript is associative map (HashMap in other programming languages) does next code
for (var prop in object) {
if (prop === someConcreteProperty) {
// some decision
break;
}
}
slower anyhow then dummy property lookup like
if (typeof object.someConcreteProperty != 'undefined') {
// some decision
}
Edits:
I'm thinking about performance in code like:
for ( var prop in obj)
if (obj[prop] === someAnonymousMethod) {
// I need that property name for my need
return obj.prop();
}
will it be twice property lookup time like
obj.prop()
or more?
Thanks.
This can be tested empirically:
<script language="javascript">
alert("Initialising test object...");
var obj = new Object();
for (var i=0; i<1000000; i++) obj["prop"+i] = i;
alert("Initialised. Doing Test.");
var d1 = (new Date()).getTime();
needle = obj["prop"+(i-1)]; // last object
for (var prop in obj) {
if (obj === needle) {
// some decision
break;
}
}
var ms1 = ((new Date()).getTime()) - d1;
alert("Method 1 took "+ms1+"ms.")
var d2 = (new Date()).getTime();
if (typeof obj["prop"+(i-1)] != 'undefined') {
// some decision
}
var ms2 = (new Date()).getTime() - d2;
alert("Method 2 took "+ms2+"ms.")
</script>
Method 1 takes MUCH longer than Method 2. This is hardly surprising since all of the computing necessary to execute Method 2 is included in Method 1 plus MUCH more.
The answer to this question becomes obvious when you understand how property lookup works in JavaScript. In the worst case, properties in JavaScript Objects are implemented as elements in a hash table.
Property lookup is, in this case, performed in constant time on average. It's good to note, though, that in very rare worst-case scenarios, hash table search time can be linear.
If you loop through a list of properties, you reduce the performance to linear time, roughly proportional to the number of properties in the object.
So, yes method 1 is always faster, and much much faster if the object has lots of properties.
Just as a side note: many modern JavaScript engines (i.e. Google's V8) may optimize your code for you to provide better performance. In fact, I believe Objects in V8 are implemented as real classes. In this case memory lookup is guaranteed to be constant time, unlike traditional hash table lookup.
I guess for the first one you meant:
if ('prop' in obj) {
// ...
}
Then you can talk about speed differences and different behavior.
Homework:
var obj = { prop: undefined }; // a bit philosophical...
This is so simple I am baffled. I have the following:
var x = 'shrimp';
var stypes = new Array('shrimp', 'crabs', 'oysters', 'fin_fish', 'crawfish', 'alligator');
for (t in stypes) {
if (stypes[t] != x) {
alert(stypes[t]);
}
}
Once the values have iterated it starts returning a dozen functions like
function (iterator, context) {
var index = 0;
iterator = iterator.bind(context);
try {
this._each(function (value) {iterator(value, index++);});
} catch (e) {
if (e != $break) {
throw e;
}
}
return this;
}
What the heck is going on?
Edit: In these scripts I am using http://script.aculo.us/prototype.js and http://script.aculo.us/scriptaculous.js I remember now reading about the way prototype extends arrays and I am betting this is part of it. How do I deal with it?
The for enumeration is going to go over every member of the object you passed it. In this case an array, which happens to have functions as members as well as the elements passed.
You could re-write your for loop to check if typeof stypes[t] == "function" or yada yada. But IMO you are better off just modifying your looping to only elements..
for(var i = 0, t; t = stypes[i]; ++i){
if (t != x) {
alert(t);
}
}
Or
for(var i = 0; i < stypes.length; ++i){
if (stypes[i] != x) {
alert(stypes[i]);
}
}
I wanted to migrate my last comment up to the answer to add the notice of the a caveat for the first type of loop.
from Simon Willison's "A re-introduction to JavaScript"..
for (var i = 0, item; item = a[i]; i++) {
// Do something with item
}
Here we are setting up two variables.
The assignment in the middle part of
the for loop is also tested for
truthfulness - if it succeeds, the
loop continues. Since i is incremented
each time, items from the array will
be assigned to item in sequential
order. The loop stops when a "falsy"
item is found (such as undefined).
Note that this trick should only be
used for arrays which you know do not
contain "falsy" values (arrays of
objects or DOM nodes for example). If
you are iterating over numeric data
that might include a 0 or string data
that might include the empty string
you should use the i, j idiom instead.
you want to do:
for (var i in object) {
if (!object.hasOwnProperty(i))
continue;
... do stuff ...
}
As for..in enumeration iterates over all properties (enumerable or otherwise) that exist on both the object and its prototype chain. The hasOwnProperty check restricts iteration to just those properties on the actual object you want to enumerate.
ES5 makes things a little better for library developers (and help avoid this stuff) but we won't see that ina shipping browser for quite a while :-(
[edit: replacing return with continue. lalalalala ;) ]
Since prototype has extended the array for your convenience you should take advantage of it. Your example could be rewritten as:
var x = 'shrimp';
var stypes = new Array('shrimp', 'crabs', 'oysters', 'fin_fish', 'crawfish', 'alligator');
stypes.without(x).each(alert);
It should be
for (t in stypes) {
if (t != x) {
alert(t);
}
}