jquery/qunit — comment on commit f24167a339e7239fd57685cca05c1cd7ac120abf
File: qunit/qunit.js line 1273
By jdalton:

It's gonna boil down to some kind of weak inference, it just depends on how much you want. I dig keeping it simple and allowing Array-like objects.
Here is [something I have in benchmark.js for its each() method](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L716-746).

~~~ js
/**
* A generic `Array#forEach` / `for...in` own property utility function.
* Callbacks may terminate the loop by explicitly returning `false`.
* @static
* @memberOf Benchmark
* @param {Array|Object} object The object to iterate over.
* @param {Function} callback The function called per iteration.
* @param {Object} thisArg The `this` binding for the callback function.
* @returns {Array|Object} Returns the object iterated over.
*/
function each(object, callback, thisArg) {
var index = -1,
result = [object, object = Object(object)][0],
isSnapshot = 'snapshotLength' in object && 'snapshotItem' in object,
skipCheck = isSnapshot || 'item' in object,
length = isSnapshot ? object.snapshotLength : object.length;

// in Opera < 10.5 `hasKey(object, 'length')` returns `false` for NodeLists
if (length == length >>> 0) {
while (++index < length) {
// in Safari 2 `index in object` is always `false` for NodeLists
if ((skipCheck || index in object) &&
callback.call(thisArg, isSnapshot ? object.snapshotItem(index) : object[index], index, object) === false) {
break;
}
}
} else {
forIn(object, callback, thisArg);
}
return result;
}
~~~

It works with Arrays, NodeLists, and XPath Snapshots.