NodeList and objects toArray(). Advice needed

I’ve been working on a function to convert nodeList and object properties to an array.

The first question is with regards IE and checking whether the object provided is an HTML collection.

The best I have come up with so far, is to test if it’s an object, has an ‘item’ which is a function and has length.

Can this be improved upon?

The second question is with regards slice.call and a while loop copy. I guess I need to do some profile/timing tests, but I’m wondering if the function merits a slice.call? or should I simplify?

Any advice greatly appreciated.

// Some weirdness in IE regarding nodesList and typeof 'item' returning 'Object'
// even though when alerted it returns 'function (){...}'.
// Therefore having to use regExp.test() to check whether it's a function instead.
// Note: _isNodeList isn't full proof. An object with the properties 
// {length: x, item : function(){}} will pass and return length.
var _isNodeList = function(obj){
  var objType = {}.toString.call(obj);
  return (objType === '[object NodeList]' || 
          objType === '[object HTMLCollection]' ||
		  objType === '[object Object]' && /^\\s?function/.test(obj.item))
		  && obj.length; // returns length of nodeList if true		  
};

var _toString = {}.toString,
    _slice = [].slice;

// ------- toArray -------
var toArray = function (obj /* HTMLCollection or Object */){
  
  var copy = [],
      len = _isNodeList(obj),
	  oType = _toString.call(obj);

  if (len){
	try {
      copy = _slice.call(obj); return copy;
    }
    catch(e) {
      while (len--) copy[len] = obj[len]; return copy;
    }
  } else if ( oType === '[object Object]' ){
	for (var prop in obj){
	  if (obj.hasOwnProperty(prop)) copy.push(obj[prop]);
    }
	return copy;
  }

  throw new TypeError ( 'Object type ' + oType + ' not supported!!' ); 
};

Tests

var obj = {length : 2, x : 3};
alert(toArray(obj)); // [2,3]

// An HTML unordered list.
var liNodeList = document.getElementsByTagName('li');
alert(toArray(liNodeList)); // [[object HTMLLIElement],[object HTMLLIElement],...,[object HTMLLIElement]]

var liItems = toArray(liNodeList);
try { liItems.forEach(function(el){ el.style.color = 'red'; }); } catch(e){}; // IE has no built-in array.forEach.

var obj2 = {length : 2, item : function(){}};
alert(toArray(obj2)); // [,] No Good!!

ps. Oh and what’s happened to crMalibu?:smiley:

I prefer a simpler method.

Array.from= function(what){
	what= what || {};
	var A= [], p, L= what.length;
	if(typeof L== 'number'){
		while(L) A[--L]= what[L];
		return A;
	}
	for(p in what){
		if(what.hasOwnProperty(p)) A[A.length]= what[p];
	}
	return A;
}

I prefer a simpler method.

Fair enough but ‘less strict’ might have been better than ‘simpler’

var fn = function(a,b,c){};
var box = {height: 3, length: 2, width: 5}

console.log(typeof fn.length === 'number') // true
console.log(typeof box.length === 'number') // true

console.log(Array.from(fn)); // [undefined,undefined,undefined]
console.log(Array.from(box)); // [undefined,undefined]

I did a quick test on the slice v loop scenario. Probably not that scientific but a few results

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>Slice Test</title></head>
<body>
<p id = 'slice'>Slice: </p>
<p id = 'loop'>Loop: </p>

<script type="text/javascript">
var start, end, i = 0, x, len,
    a = [], 
    slice = [].slice,
	sliceP = document.getElementById('slice'),
    loopP = document.getElementById('loop');
	
for (;i < 10000; i++) a[i] = i;
	
start = new Date();
for (i = 0; i < 1000; i++){
  x = slice.call(a);
}
end = new Date() - start;
sliceP.innerHTML += end + 'ms'; 
// slice - FF 38ms Chrome 3ms IE 439ms Safari 224ms


start = new Date();
for (i = 0; i < 1000; i++){
  len = 10000;
  while(len) x[--len] = a[len];
}
end = new Date() - start;
loopP.innerHTML += end + 'ms'; 
// loop - FF 433ms Chrome 110ms IE 3227ms Safari 84ms
</script>
</body>
</html>

Safari threw a curve ball, but as far as FF, Chrome and IE it seems slice is considerably faster.