looking for a full-immersion/face-to-face HTML5, JavaScript, and Mobile Web Development training in the heart of London? Book your spot
Showing posts with label Array. Show all posts
Showing posts with label Array. Show all posts

Wednesday, November 30, 2011

Array extras and Objects

When Array extras landed in JavaScript 1.6 I had, probably together with other developers, one of those HOORRAYYY moment ...
What many libraries and frameworks out there still implement, is this sort of universal each method that supposes to be compatible with both Arrays and Objects.

A Bit Messed Up

What I have never liked that much about these each methods is that we have to know in advance in any case if the object we are passing is an Array, an ArrayLike one, or an Object.
In latter case, the callback passed as second argument will receive as second argument the key, and not the index, which simply means we cannot trust a generic callback unless this does not check per each iterated item the second argument type, or unless we don't care at all about the second argument.
In any case I always found this a bad design. If we think about events, as example, it's totally natural to expect a single argument as event object and then we can act accordingly.
This let us reuse callbacks for similar purpose and maintain a DRY code.

Need For An Object#forEach

All implementation of each, and as far as I know with the only exception of jQuery which makes things even more complicated since we generally have to completely ignore the first argument in this case, have some natural confusion inside the method.
If you take the underscore.js library, as example, you will note that there are two aliases for the each method, each itself and forEach, so it's more than clear for me that JS developers are clearly missing an Array#forEach like method in order to iterate with objects, rather than lists.
It must be also underlined that all these methods are somehow error prone: what if the object we are passing has a length property that does not necessary mean it points to the length of items stored via index as if it was an Array?
You may consider this an edge case, or an anti pattern, then you have to remember that functions in JavaScript are first class objects.
Probably all these methods will nicely fail indeed with functions, passed as objects, whenever you decide that your function can be used as object too.

var whyNot = function (obj) {
/* marvelous stuff here */
this.calls++;
return this.doStuff(obj);
};
whyNot.calls = 0;
whyNot.doStuff = function (obj) {
/* kick-ass method */
};

// the unexpected but allowed
whyNot = whyNot.bind(whyNot);

whyNot.length; // 1
whyNot[0]; // undefined

By design, the length of any function in JavaScript is read-only and means nothing, in therms of Array iteration, it simply means the number of arguments the function defined during its declaration/definition as expression.

WTF

Whenever above example makes sense or not, I am pros patterns exploration and when a common method is not compatible with all scenarios, I simply think something went wrong or is missing in the language.
Thanks gosh JS is freaking flexible and with ES5 we can define some prototype without affecting for( in ) loops but hopefully simplifying our daily basis stuff.
Remember? With underscore or others we still have to know in advance if the passed object is an Array, an ArrayLike, or a generic object ... so what would stop us to simply chose accordingly?

// Array or ArrayLike
[].forEach.call(genericArrayLike, callbackForArrays);

// generic object to iterate
{}.forEach.call(object, callbackForObjects);

An explicit choice in above case is the fastest and most reliable way we have to do things properly. A DOM collection, as well as any array or arrayLike object will use the native forEach, but we can still recycle callbacks designed to deal with value, key and objects, rather than value, index, and this is the little experiment:

Object extras

The concept of each callback is exactly the same of original, native, Array callbacks, except things are based on native functions available in all ES5 compatible desktop and basically all mobile browsers, and easy to shim with all others too old to deal with JS 1.6 or higher.



Here a couple of examples:

var o = {a:"a", b:"b", c:""};

// know if all values are strings
o.every(function (value, key, object) {
return typeof value == "string";
}); // true

// filter by content, no empty strings
var filtered = o.filter(function (value, key, object) {
return value.length;
}); // {a:"a",b:"b"} // original object preserved

// loop through all values (plus checks)
o.forEach(function (value, key, object) {
object === o; // true
this === o; // true
if (key.charAt(0) != "_") {
doSomethingWithThisValue(value);
}
}, o); // NOTE: all these methods respect Array extras signatures

// map a new object
var mapped = o.map(function (value, key, object) {
return value + 1;
}); // {a:"a1",b:"b1"} // original object preserved

// know if a value contains "a"
o.some(function (value, key, object) {
return value === "a";
}); // true

The reason reduce and reduceRight are not in the list is simple: which one would be the key to preserve, the first of the list? There is no such thing as "predefined for/in order" in JavaScript plus these methods are more Array related so out of this experiment.

As Summary

Once minified and minzipped the gist weights about 296 bytes which is ridiculous size compared with any application we are dealing with on daily basis.
Specially forEach, but probably others too, may become extremely handy and ... of course, using the Object.keys method internally, this is gonna be compatible with Arrays too but hey, the whole point was to make a clear distinction ;)


[edited]

The Misleading Signature

I don't know how many times I have spoken with jQuery developers, just because they are common, convinced that native Array#forEach was accepting the value as second argument.
I always considered inverted signatures, whatever API it is, bad for both performances, no possibility to fallback into some native method, and learning curve, where new comers learn than a generic each method must have the index as first argument.
Bear in mind whenever we loop we are most likely interested into the value of that index or key, so this value should be the first, and if you need the only one, argument passed through the procedure.
A completely ignored first argument is, once again and in my opinion, a bad design for an API: stuck without native power, teaching arguments order is not relevant.
Well, specially latter point is true if we have named arguments, but in JS nothing have been planned so far, and in ES6 the way we gonna name arguments is still under discussion.

Have fun with JS

Tuesday, July 27, 2010

Array.prototype.slice VS Arrayfication

One of the most common operations performed on daily basis directly or indirectly via frameworks and libraries is Array.prototype.slice calls over non Array elements such HTMLCollection, NodeList, and Arguments.

Why We Perform Such Operation


The Function.prototype.apply works only with object created through the [[Class]] Array or Arguments.
In latter case we may like to avoid ES3 arguments and named arguments mess when dealing with indexes.
Finally, in most of the case we would like to perform Array operations over ArrayLike objects to filer, map, splice, change, modify, etc etc ...

The Slice Cost


Let's perform over an ArrayLike object of length 2000, 2000 slice calls (change the length if you have such powerful machine):

// create the ArrayLike object
for(var arguments = {length:2000}, i = 0; i < 2000; ++i)
arguments[i] = i;
;

// define the bench function
function testSlice(arguments) {
var t = new Date;
for (var slice = Array.prototype.slice, i = 0, length = arguments.length; i < length; ++i)
slice.call(arguments);
;
return new Date - t;
}

// test it
alert(testSlice(arguments));

The average time in my Atom N270 based Netbook is 1750 ms, and numbers are not that unreasonable.
While a list of 2000 generic values may be not that common, the number of slice.call may be definitively more than 2000 during an application life cycle. All this wasted time to simply transform a generic list into an Array? And maybe just to loop and re-loop over it to filter or change values and indexes?

Alternative One: __proto__


If we modify the __proto__ property of whatever object and in almost all browsers (not IE, of course), we can somehow "promote" the current object to Array, except for the [[Class]] that will still be the generic Object, or Arguments.
The undesired side effect is that the modified object won't find anymore in its prototype its original methods, so we can define this technique really greedy.
But what about performances?

// function to test
function testProto(arguments) {
var t = new Date;
for (var proto = Array.prototype, i = 0, length = arguments.length; i < length; ++i)
arguments.__proto__ = proto;
;
return new Date - t;
}

// the result using precedent arguments object
alert(testProto(arguments));

The average elapsed time in this case is 40 ms but we have to remember is that we are not converting the object into an Array instance, we are simply overwriting it's inherited properties and methods with those part of the Array.prototype object.
This means that push, unshift, splice, forEach, or other operations, will be accessible directly through the object (e.g. arguments.forEach( ... ))
If these methods are the reason we would like to slice the generic object, this solution is definitively preferred.

Alternative Two: Arrayfication


To avoid the undesired side effect obtained via __proto__ assignment, the removal of inherited methods, we may consider to use call or apply directly via Array.prototype.
The imminent side effect of this technique is that potentially every function may like to use one of the Array prototype methods against the same object which is always passed by reference.
A simple solution could be the one to attach directly a method to this object, so that every part of the application will be able to use, as example, a forEach call for this object, without accessing every time the Array.prototype.forEach method.

arguments.forEach = Array.prototype.forEach;
// now use forEach wherever we need
// with arguments object

Since every function may like to use one or more Array methods, how about creating a function able to attach all of them in one shot?

Array.fy = (function () {
// (C) WebReflection - Mit Style License
for (var
m = [
"pop", "push", "reverse", "shift", "sort", "splice", "unshift",
"concat", "join", "slice", "indexOf", "lastIndexOf",
"filter", "forEach", "every", "map", "some", "reduce", "reduceRight"
],
i = m.length; i--;
) {
m[i] = "o." + m[i] + "=p." + m[i];
}
m.push("return o");
return Function("p",
"return function Arrayfy(o){" + m.join(";") + "}"
)(Array.prototype);
}());

With a single call we can attach all current available Array.prototype methods once without getting rid of current inherited properties or methods.
Let's see how much does a call cost:

// test function for Array.fy
function testArrayfy(arguments) {
var t = new Date;
for (var fy = Array.fy, i = 0, length = arguments.length; i < length; ++i)
fy(arguments);
;
return new Date - t;
}

// bench
alert(testArrayfy(arguments));

The average elapsed time for this operation is 3 ms.
After a single call we can consider the generic object an Array duck, preserving its inheritance.
The greedy aspect is about possible overwrites, but I have personally never called a method forEach if this is not exactly representing the Array.forEach method.

Arrayfied Operations


The last benchmark we can do is about common Array operations over our Arrayfied object.
The first consideration to do is that slice, as every other Array operation, seems to be extremely optimized for real Array objects.
In few words if we need to transform because we need many calls to forEach, filter, map, slice, etc, the transformation via slice is probably what we are looking for.
But if we need a generic loop over a generic callback and just few times, Array.fy proposal is probably the most indicated one. Here some extra test:

// slice plus a forEach operation
function testSliceEach(arguments) {
var t = new Date;
for (var slice = Array.prototype.slice, fn = function() {}, i = 0, length = arguments.length; i < length; ++i)
slice.call(arguments).forEach(fn);
;
return new Date - t;
}

// just forEach through Arrayfied object
function testArrayfied(arguments) {
var t = new Date;
Array.fy(arguments);
for (var fn = function() {}, i = 0, length = arguments.length; i < length; ++i)
arguments.forEach(fn);
;
return new Date - t;
}

// just slice through Arrayfied object
function testArrayfiedSliced(arguments) {
var t = new Date;
Array.fy(arguments);
for (var fn = function() {}, i = 0, length = arguments.length; i < length; ++i)
arguments.slice();
;
return new Date - t;
}

// bench

alert(testSliceEach(arguments)); // 3200ms
alert(testArrayfied(arguments)); // 2400ms
alert(testArrayfiedSliced(arguments));// 1700ms

The last test is against a classic slice.call and it costs basically the same.
First and seconds demonstrate that if we slice to use native power we are actually spending more time than using native power directly.
Bear in mind that if the variable is already an Array, slice will cost much less and the average against the last test will be 1350 ms.

IE And Conclusions


I keep saying that IE should simply have normalized Array.prototype, ignoring those person that wrongly rely in for in loops over arrays when it's not necessary, and making this Array.fy portable for IE world as well since the internal proto variable is a pointer to the original Array.prototype then automatically ready for natives standard enhancements.
The nice part is that rather than see Array.prototype.something.call in every piece of code we can easily use one fast call to have them all ... so, you decide :-)

.. last minute Example ...


Just because sometimes we lack of fantasy, here a generic usage for Array.fy:

function $(selector, parentNode) {
return Array.fy((parentNode || document).querySelectorAll(selector));
}

$("div").forEach(function (div, i, nodeList) {
div.innerHTML = "Array.fy rocks!";
});


Update
just for testing/performances purpose:

Array.ify = (function () {
// (C) WebReflection - Mit Style License
for (var
m = [
"pop", "push", "reverse", "shift", "sort", "splice", "unshift",
"concat", "join", "slice", "indexOf", "lastIndexOf",
"filter", "forEach", "every", "map", "some", "reduce", "reduceRight"
],
f = [],
i = m.length; i--;
) {
f[i] = "var " + m[i] + "=p." + m[i];
m[i] = "o." + m[i] + "=" + m[i];
}
m.push("return o");
return Function("p", f.join(";") +
";return function Arrayfy(o){" + m.join(";") + "}"
)(Array.prototype);
}());

Sunday, April 19, 2009

[COW] A Generic ArrayObject Converter

Few days ago I wrote about a fast Array slice implementation for every browser, a callback able to convert "every collection" into an Array.
For collection, I mean every instance with a length attribute and an index access Array like. This object, for example, could be considered as a collection:

var generic = {
length: 2,
"0": "abc",
"1": "def"
};

Above instance is a basic model of most common libraries such jQuery, prototype, base, and every other based over an Array like prototype.

Some library could coexist in the same page without problems but not every library implement a method to create a copy of another collection into a new instance of the library itself.
As example, a jQuery or Sizzle result into another instance, a generic DOM collection into a jQuery object, etc etc.
All these instances could be passed via Array.prototype.push to be populated, and thanks to this peculiarity we can obtain every kind of instance from a collection via Array conversion.

var toArrayObject = (function(push, e){
// WebReflection - Mit Style License
e.base = {length:0};
e.slice = (function(slice){
// portable slice
try {
slice.call(document.childNodes);
var $slice = slice;
} catch(e) {
var $slice = function(begin, end){
if(this instanceof Object)
return slice.call(this, begin || 0, end || this.length);
for(var i = begin || 0, length = end || this.length, len = 0, result = []; i < length; ++i)
result[len++] = this[i];
return result;
};
};
return $slice;
})(Array.prototype.slice);

// prototype ready callback
return function(constructor){

// assign the "class" prototype or the base object
// as anonymous prototype
e.prototype = constructor ? constructor.prototype : e.base;

// create a new instance of anonymous
var r = constructor === Array ? [] : new e;

// inject via push the collection, passed as function scope
push.apply(r, e.slice.call(this));

// return the new ArrayObject instance
return r;
};
})(
Array.prototype.push,
function(){}
);

With above code we could quickly transform, for example, a DOM collection into a jQuery instance without using the jQuery engine:


var result = toArrayObject.call(
document.getElementsByTagName("div"),
jQuery
);

result.remove();


Another example could be a transformation between jQuery and prototype:

var prototyped = toArrayObject.call(jQuery(".test"), $);


The same could be obviously done via Array, even if this is a non-common case:

toArrayObject.call([1, 2, 3], jQuery);
toArrayObject.call(jQuery(".test"), Array);


The usage of call makes prototype assignment that simple:

jQuery.fn.convertTo = toArrayObject;

// as prototype or other library conversion
jQuery(".test").convertTo($);


That's it, if you never wondered about elements injections, libraries conversions, or Array like object management, this code could be a truly quick and portable solution.

Friday, April 03, 2009

A fast Array slice for every browser

It is an extremely common task and one of the most used prototype in libraries and applications: Array.prototype.slice
The peculiarity of this prototype is to create an Array from an Array like Object such arguments, HTMLCollection, other kind of lists as jQuery results.

Every browser, except Internet Explorer, allows direct calls via native prototype obtaining, obviously, best performances.

In IE, we have different ways to make this task possible, and these ways are mainly classic loops, or the isArray check to know when it is possible to perform the native call or not.


isArray = (function(toString){
return function(obj){
return toString.call(obj) === "[object Array]";
};
})(Object.prototype.toString);

Even using a closure, above function could slow down performances, specially in those libraries where Array conversions are performed almost everywhere.

As we all know, Internet Explorer behaves weird "sometimes", and one of the weirdest things is that native Objects are not instanceof Object.

In few words, instead of call a function to define if native slice could be applied, all we need to do is to check if the generic object is instanceof Object.
In this way native Arrays, arguments, everything with a length user defined, will be passed via native Array.prototype.slice, while for native objects, the good old loop will do the dirty job.

A better general purpose slice

$slice = (function(slice){
// WebReflection - Mit Style License
try {
// Chrome, FireFox, Opera, Safari, WebKit
slice.call(document.childNodes);
var $slice = slice;
} catch(e) {
// Internet Epxlorer
var $slice = function(begin, end){
// false with native objects/collections
// suitable for Array like Object (e.g arguments, jQuery, etc ...)
if(this instanceof Object)
return slice.call(this, begin || 0, end || this.length);
// ... every other case ...
for(var i = begin || 0, length = end || this.length, len = 0, result = []; i < length; ++i)
result[len++] = this[i];
return result;
};
};
return $slice;
})(Array.prototype.slice);

Simple, isn't it? And here a test case:
(function(){
try {

// HTMLCollection [object, ...]
$slice.call(document.getElementsByTagName("*"));

// arguments [3,2,1]
$slice.call(arguments);

// Array [1,2,3]
$slice.call([1,2,3]);

} catch(e) {

// should never happen
alert(e.message);
};
})(3,2,1);

where if nothing happen, simply means $slice worked without problems.

Side effects? With sandbox variables (iframes) IE will always use the loop, unless we do not bring its native Object constructor into the function (or the function into the sandbox)

Saturday, December 06, 2008

A fast and crossbrowser function to make an Array

An absolutely common task present in almost every library, is to transform a generic collection/list of objects or DOM Elements into a friendly Array.

Cases Scenario



  • It is possible to apply directly an Array.prototype.slice call to quickly return an Array

  • It is not possible at all apply every kind of Array prototype to the list



Cases scenario detection


When we execute a piece of JavaScript in a web page, we can assume that we have at least one DOM element in that page, as the script itself for example, so it is always possible to obtain a collection of elements calling document.getElementsByTagName("script") or a generic ("*") in this case probably superflous considering the latter assumption.

Cross browser and fast makeArray proposal



var makeArray = function(push, slice){
try{
// Andrea Giammarchi proposal
slice.call(document.getElementsByTagName("script"), 0);
return function(array, results){
array = array instanceof Array ? array : slice.call(array, 0);
results ? push.apply(results, array) : results = array;
return results
}
}catch(e){
return function(array, results){
if(!(array instanceof Array))
for(var ret = [], i = 0, length = array.length; i < length; i++)
ret[i] = array[i];
else
var ret = array;
results ? push.apply(results, ret) : results = ret;
return results
}
}
}(Array.prototype.push, Array.prototype.slice);


Summary


The try catch is executed only once, the Array prototypes are cached once as well to guarantee cross libraries compatibility. The function is assigned differently for those browsers that can apply the slice prototype to the collection and those that cannot (mainly Internet Explorer).
Accepted parameters are two, inspired by Sizzle function, to allow us to concatenate elements into a collection.

The collection is transformed into an Array only if necessary, since it does not make sense to perform a slice call in every case.

Compatiblity? It should be every browser that supports try and catch statement :-)

Tuesday, May 20, 2008

Habemus Array ... unlocked length in IE8, subclassed Array for every browser

History


I do not know how many time, during these years, JavaScript Ninjas tried to subclass the native Array to create libraries over its powerful methods without losing performances. I have finally discovered the way to remove locked length from Internet Explorer 8, and to solve problems with every other browser.

We tried to inherit Array instead of Object


This is where my last trip started, simply looking at arguments behaviour. It was there, since 2000 when I started to code in JavaScript, and it was so simple that probably few developers thought about them!

var o = {
length:0,
push:Array.prototype.push,
toString:Array.prototype.join
};

o.push(1,2,3);
alert(o); // 1,2,3

arguments, in JavaScript, is an instanceof Object, and not an Array, as is in ActionScript since version 1.0
What we have done all this time, is to use Array.prototype methods injecting a basic object, with a simple length parameter, inside.
If an object with a length value can be used as an Array, why on heart above code should not work?

We all love prototypal inheritance, we all want an instanceof Array


If you try to inherit directly an array as prototype, Internet Explorer will fix every instance length property, destroying possibility to use simple for loop over generated values.

function MyArray(){};
MyArray.prototype = [];

var a = new MyArray;
a.push(1,2,3);
alert(a.length); // 0 with every Internet Explorer

Problems are much more than a fixed length, as I wrote many months ago when I presented my ArrayObject.
On the other hand, this kind problem has been fixed for Internet Explorer 8, and 7 emulation.
Yes, finally I did it!

/**
* Choose a name for subclassed Array
*/
Stack = (function(){ // (C) Andrea Giammarchi - Mit Style License

/**
* Your personal Array constructor
*/
function Stack(length){
if(arguments.length === 1 && typeof length === "number")
this.length = -1 < length && length === length << 1 >> 1 ? length : this.push(length);
else if(arguments.length)
this.push.apply(this, arguments);
};

// Solution 1:
// Declaration of generic function
// with an array as prototype
function Array(){};
Array.prototype = [];

// Solution 2:
// use the prototype chain to inherit
// Array constructor and its native prototype
Stack.prototype = new Array;

// Solution 3:
// overwrite inherited length with zero value
Stack.prototype.length = 0;

// Solution 4:
// redeclare toString method in this way
// to let JScript core feel better
Stack.prototype.toString = function(){
return this.slice(0).toString();
};

/**
* Return and assign subclassed Array
*/
Stack.prototype.constructor = Stack;
return Stack;

})();

Above code is the basis to create an alternative Array constructor that will be able to work as expected with every browser plus IE8, without length problems.
If you try to remove a single comma from some Solution, it will never work.
If you directly assign an array to the prototype, length will be fixed.
If you remove toString prototype, FireFox and others will not work as expected.

The definitive workaround for every browser


Since first part of this post could be used in every browser, starting from IE 5.5, these old browser can simply use a constructor with a prototype full of native methods, but without instanceof Array behavior.
At the same time, every other cool browser (Safari, Firefox, Opera) could use above code to have the same behavior of IE8.

This is the full cross browser Stack constructor

While this is an improvement over basic JS 1.5 Array, to have JS 1.7 methods too, natives with updated browsers, emulated in a fast standard way with every other.
Stack Extended JS 1.7 Subclassed Array

The last problem to solve, the concat method


concat, is as simple as truly bastard prototype!
There is no way to use native concat method, even with prototypal chain inherited native Array instances.
This is why I have normalized that method, in a Stack self compatible way.
On the other hand, you cannot send a Stack instance as concat parameter, but you can always use native slice method, fast as native one is.

Best performances ever


Yes, using native, incore, prototypes, makes your code execution faster.
This compatibility + benchmark page, can tell you more about this Stack implementation than me, specially with IE8, Safari, and Opera, where performances are neary the same of a generic Array.

FireFox is probably the one that has more problems to manage native code with dynamic constructors, but hey, I am talking about Firefox beta 3, while probably RC1 or nex release will be fast as Safari, or Opera, are.

What to do with Stack?


Libraries, libraries, and libraries, finally with core performaces, the possibility to truly extend the Array, removing every fake iframe, popup, whathever you have used during these days.

Have fun with Stack, and see you soon for some other cool example with them :geek:

Sunday, March 02, 2008

JavaScript ArrayObject

I think this constructor could be a good start point for a lot of projects.

We know that IE has problems while it try to extend an array, disabling length modification.

That's why I have created this wrapper that does not require weird strategies (e.g. iframe with a different enviroment) and works with a wide range of browsers.

Honestly, I did not test performances against pure arrays, but I did everything to make code as fast as possible, using good practices and creating an in-scope shortcut for the Array.prototype.

Have a look here if you are interested in this constructor, ignore them if you do not think this is a good idea :)

P.S. Please note that this constructor does not care about missed prototypes, it only does its wrapping work and nothing else. To improve Array compatibility and methods, please remember my JSL Revision. Including them in your page, You'll not have problems with every ArrayObject method, eccept for reduce and reduceRight (I will create a good implementation of those function, I promise!)