array like objects

Mike Samuel mikesamuel at gmail.com
Tue Dec 8 10:10:20 PST 2009


2009/12/8 Erik Corry <erik.corry at gmail.com>:
> 2009/12/8 Mike Samuel <mikesamuel at gmail.com>:
>> It occurred to me after looking at the proxy strawman that it might
>> help to nail down what "array-like" means in future drafts.  It isn't
>> directly related to the proxy stuff though so I thought I'd start a
>> separate thread.
>>
>> I've seen quite a bit of library code that does something like
>>   if (isArrayLike(input)) {
>>     // iterate over properties [0,length) in increasing order
>>   } else {
>>     // Iterate over key value pairs
>>   }
>
> This looks fairly broken to me.  If the object has enumerable
> properties that aren't positive integers then they don't get iterated
> over just because some heuristic says it's array-like.  If the
> heuristic says it's array-like then we iterate over portentially
> billions of indexes even if it is very sparse.

All true.  And yet it is not uncommon.  See the bottom of this email
for a quick survey of a number of libraries' uses of the array-like
concept.




> I think there are two different questions being asked here.
>
> 1) Does the object have the special semantics around .length?  This is
> potentially useful to know and the normal way to find out doesn't
> work.  Instanceof is affected by setting __proto__, yet the special
> .length handling persists and instanceof doesn't work for cross-iframe
> objects.

I think I agree with the bit about length, but __proto__ doesn't work
on non mozilla interpreters and isn't likely to be standardized.  I
don't see how it's relevant to array-like-ness.

> 2) Is it more efficient to iterate over this object with for-in or is
> it more efficient (and sufficient) to iterate with a loop from 0 to
> length-1?  You can't implement functions like slice properly without
> this information and there's no way to get it.

Efficiency is obviously important, but another important distinction
is whether 'length' should be included in the set of keys iterated
over, and whether iteration over array-index keys should be in numeric
order.

>> but different libraries defined array-like in different ways.
>> Some ways I've seen:
>>    (1) input instanceof Array
>>    (2) Object.prototype.toString(input) === '[object Array]'
>>    (3) input.length === (input.length >> 0)
>> etc.
>
> These all look like failed attempts to answer one or both of the two
> questions above.

Agreed.  Some more attempts below.



>>
>> The common thread with array like objects is that they are meant to be
>> iterated over in series.
>> It might simplify library code and reduce confusion among clients of
>> these libraries if there is some consistent definition of series-ness.
>> This committee might want to get involved since it could affect
>> discussions on a few topics:
>>   (1) key iteration order
>>   (2) generators/iterators
>>   (3) catchall proposals
>>   (4) type systems
>>
>> One strawman definition for an array like object:
>>    o is an array like object if o[[Get]]('length') returns a valid
>> array index or one greater than the largest valid array index.
>>
>> The need to distinguish between the two in library code could be
>> mooted if for (in) on arrays iterated over the array index properties
>> of arrays in numeric oder order first, followed by other properties in
>> insertion order, and host objects like NodeCollections followed suit.
>
> FWIW V8 does this for both arrays and objects.
>
> But really for..in is pretty sick for arrays.  It will convert every
> single index in the array to a string.  That's not something to be
> encouraged IMHO.

Also agreed.


> --
> Erik Corry



Prototype.js dodges this problem by adding an each method to
Array.prototype and others.  This obviously fails for cross-context
Arrays, and also suffers from the large sparse array and missing
non-array-index properties problems.

It does switch on arrays in places, and does so inconsistently though
-- in one case (unnecessarily?) filtering out arguments objects?
      if (Object.isArray(item) && !('callee' in item)) {
        for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
          array.push(item[j]);
      } else {

  function flatten() {
    return this.inject([], function(array, value) {
      if (Object.isArray(value))
        return array.concat(value.flatten());
      array.push(value);
      return array;

dojo.clone does
              if(d.isArray(o)){
			r = [];
			for(i = 0, l = o.length; i < l; ++i){
				if(i in o){
					r.push(d.clone(o[i]));
				}
			}
		}else{
			// generic objects
			r = o.constructor ? new o.constructor() : {};
		}
       dojo.isArray = function(/*anything*/ it){
		//	summary:
		//		Return true if it is an Array.
		//		Does not work on Arrays created in other windows.
		return it && (it instanceof Array || typeof it == "array"); // Boolean
	}
	dojo.isArrayLike = function(/*anything*/ it){
		//	summary:
		//		similar to dojo.isArray() but more permissive
		//	description:
		//		Doesn't strongly test for "arrayness".  Instead, settles for "isn't
		//		a string or number and has a length property". Arguments objects
		//		and DOM collections will return true when passed to
		//		dojo.isArrayLike(), but will return false when passed to
		//		dojo.isArray().
		//	returns:
		//		If it walks like a duck and quacks like a duck, return `true`
		return it && it !== undefined && // Boolean
			// keep out built-in constructors (Number, String, ...) which have length
			// properties
			!d.isString(it) && !d.isFunction(it) &&
			!(it.tagName && it.tagName.toLowerCase() == 'form') &&
			(d.isArray(it) || isFinite(it.length));
	}
and dojo uses these definitions to do type coercion
			}else if(!dojo.isArray(content)){
				//To get to this point, content is array-like, but
				//not an array, which likely means a DOM NodeList. Convert it now.
				content = dojo._toArray(content);


The closure library similarly defines both isArray and isArrayLike,
but with differences (
http://code.google.com/p/closure-library/source/browse/trunk/closure/goog/base.js
):
    goog.isArrayLike = function(val) {
      var type = goog.typeOf(val);
      return type == 'array' || type == 'object' && typeof val.length
== 'number';
    };
which is then used in many places (
http://www.google.com/codesearch?q=goog.isArrayLike+package%3Ahttp%3A%2F%2Fclosure-library%5C.googlecode%5C.com&origq=goog.isArrayLike
), e.g. in iter.js
    goog.iter.forEach = function(iterable, f, opt_obj) {
      if (goog.isArrayLike(iterable)) {


MooTools is the only library I looked at that does not check
array-likedness.  They do something similar to prototype by
monkey-patching Array, which will fail for cross-context arrays.  They
do extend the concept of length to their Hash class:
	getLength: function(){
		var length = 0;
		for (var key in this){
			if (this.hasOwnProperty(key)) length++;
		}
		return length;
	}


More information about the es-discuss mailing list