Skip to Content

[Prototype | http://prototype.conio.net/] library has a wonderful function named $A. Btw, funny enough phenomena with naming convention used for global functions in Ajax libraries – if it is soooo global and useful then it deserves a

$

😉 Ok, $A is really useful, personally, I’d added several more

$$

to it.

Prototype library intensively used so-call parameter binding (or “curling”). I’ll discuss this in later post, but for now just imaging the following: if function receives less arguments then expecting, it returns function with same behavior but with shorter arguments list while part of parameters are “bound” to original argument values:


function mult() {
switch (arguments.length) {
case 0: return mult;
case 1:
var a = arguments[0];
var mult_by = function() {
return arguments.length ? a * arguments[0] : mult_by;
};
return mult_by;
default: return arguments[0] * arguments[1];
}
}

var a = mult( 19, 77 ); // 1463
var b = mult( 8, 9); // 72
var twice = mult(2); // mult with first parameter bound to 2
var c = twice(14.5) // 29

 

Hope you got the idea. So Prototype uses this technique intensively, but actual implementation is slightly different. Sure, it’s done in more generic manner. And to support this “generic manner” it’s necessary to clone original arguments of function to Array, modify it and further pass to “curled” nested function via Function.apply.

However, when I read the code for $A function I was shocked. It’s hard to imaging more inefficient implementation:


var $A = Array.from = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) {
return iterable.toArray();
} else {
var results = [];
for (var i = 0; i < iterable.length; i++)
results.push(iterable[i]);
return results;
}
}

 

First line is obvious – if we pass something that do not evaluates to true (null, undefined, empty string (?), zero(?), NaN(?) ) we just return empty array. Next is obvious too: if object supports own method to get array we use it. The broken code is actually “manual” copying of array-like objects.

“Array-like” is term used to describe JavaScript object that walks and quacks like Array: they have integer-indexed properties and a length property that describes number of indexed properties.

If you’ve ever been on other side of JavaScript, i.e. you’ve created own host objects for Rhino or SpiderMonkey interpreter, you may share my frustration. See, any “array-like” has length property, so we (or they, Prototypers) know size of result in advance. We can use Array(size) constructor to allocate necessary memory for result only once and then just set every element by index, instead of pushing elements one by one and resizing array with every operation. Do I need to clarify what is more efficient? I bet no…

But the inefficiency doesn’t end here. The way original iterable is iterated is an example of bad coding as well.  Think, how many times iterable.length is calculated in loop? One? You are kidding, this is not binary code compiled by C compiler! iterable.length is calculated iterable.length times. Plain dumb. And final performance hint: iteration works faster for Aray if it is done in reverse order. Don’t ask me why – it simply works this way. You can find plenty of related tests with Google. And for logic of this code block, iteration order doesn’t matter. So, final result is:


var $A = Array.from = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) {
return iterable.toArray();
} else {
var cnt = iterable.length;
var results = new Array(cnt);
for (var i = cnt - 1; i >= 0; i--)
results[i] = iterable[i];
return results;
}
}

 

Readable/Explicit/Simple? Yes. Yes. Have the same size? Yes. And far more efficient.

Oh, the quiz question. Just for academic interest, how many chars are necessary to copy “array-like” object to JavaScript Array? Efficiency aside 😉 Given:

var a = ;

var b = <expression>;

alert(“Size: ” + b.length + “\n” + b.join(“\n”));

 

Your turn!

To report this post you need to login first.

22 Comments

You must be Logged on to comment or reply to a post.

    1. Valery Silaev Post author
      Craig,

      Thanks for promotion! 😉

      To those who whant to participate: note, I know only good enough (or “short enough” for this matter) solution. You have a great chance to come up with something outstanding that beats my own answer! But I promise to provide my answer “as is” even in this case 😉

      Valery

      (0) 
      1. I started off with the simplest way:<br/><br/>var a = ;<br/>var size=a.length;<br/>var b=new Array(size);<br/>for (var i=0;i<size;i++)<br/>     b[i]=a[i];alert(b);<br/><br/>But then I figured — why bother? “a” is basically an Array, but just without the label of an Array… I can treat it as one because the keys to the values (the values being A, B, C) are numbers — 0, 1, 2 — just like in an Array. So, this would work just fine:<br/><br/>alert(a[0])<br/><br/>Just like it’s an Array. What I’m missing is the functions… If I need the join() function I plug it in:<br/><br/>a.join = Array.prototype.join<br/><br/>and voila! I can run join() on the object a:<br/><br/>alert(a.join());<br/><br/>will return “A, B, C”. This will work for any Array object… 🙂 I doubt this is what you were going for, but it’s another way to show how versatile this nifty JavaScript is :-)<br/><br/><br/>Cheers,<br/>Alon

        (0) 
        1. Valery Silaev Post author
          Alon,

          You are cheating ;)))
          The actual question (right above code) states that you must convert array-like to true Array object.

          If you’d like, I can add more conditions to met:
          b instanceof Array === true;
          b.constructor === Array;

          But I guess it’s obviuos what is really necessary 🙂

          Valery

          (0) 
      2. Dagfinn Parnas

        Two solutions<br/>One straight forward(39 characters)<br/>b=[];for(i=0;b.push(a[i+])<a.length;);<br/><br/>And one using regexp (54 characters), not very readable but that was hardly the goal :)<br/>for(b=[];b.push(/./.exec(a[b.length])[0])<a.length;);<br/><br/>The regexp could have been much shorter, but couldn’t find a way to return all matches at once.<br/><br/>(another alternative for(b=[],i=0;b.push(/./.exec(a[i+])[0])<a.length;); is one character shorter, but much more boring)

        (0) 
        1. Dagfinn Parnas

          The last sentence should be :<br/>another alternative for(b=[],i=0;b.push(/.*/.exec(a[i++])[0])<a.length;); is one character shorter, but much more boring<br/><br/>Hopefully it will come through this time

          (0) 
          1. Dagfinn Parnas

            One more try. <br/>It seems it interprets <a.length as the start tag of an HTML element.<br/><br/>Two solutions<br/>One straight forward(39 characters)<br/>b=[];for(i=0;b.push(a[i+])<a.length;);<br/><br/>And one using regexp (54 characters), not very readable but that was hardly the goal :)<br/>for(b=[];b.push(/./.exec(a[b.length])[0])<a.length;);<br/><br/>The regexp could have been much shorter, but couldn’t find a way to return all matches at once.<br/><br/>(another alternative for(b=[],i=0;b.push(/./.exec(a[i+])[0])<a.length;); is one character shorter, but much more boring)

            (0) 
            1. Valery Silaev Post author
              Dagfinn,

              Thanks for participating!

              The stuff with RegExp just crack my head — never ever guess RegExp can be applicable 😎

              If Craig read this, I’d like to ask him to assign at least 10 points for you — you win in “The most surprising solution” nomination!

              But, actually, I’m not fan of imperative language constructs, so you get first hint — “for” loop is unnecessary.

              Valery

              (0) 
              1. Dagfinn Parnas
                Yes, removing the for loop was one of my first goals as well. But I couldn’t find any way to flatten/export the array-like structure into a string or some other primitive form.
                (0) 
                1. Valery Silaev Post author
                  Dagfinn,

                  Flattern to primitive form??? To string???

                  Hint: array-like… like an array… almost the same as an array… :))

                  Valery

                  (0) 
                  1. Dagfinn Parnas
                    yes, if I got it flat, I had hoped I could use regexp or some other string option to build it up as a proper array.

                    I’ve also had a quick look at changing the prototype, inheritFrom and/or constructor methods on the a object, but they all failed, so I have officially given up 🙂

                    (0) 
  1. Pran Bhas
    The closest I could come up is a variation

    b = [];for(var i in a)b.push(a[i]);

    I racked my brains over this couldnt get Object literals to be type converted to String,Array neither implicitly or using other hacks.

    Keep it coming..

    (0) 
    1. Valery Silaev Post author
      Pran,

      Believe me or not you repeat Eddy’s solution again ;))

      Considering shift between time zones you both leave, we can even assume that you provided same answer at the same time 🙂

      However, shortes expression exists (without loop).

      Valery

      (0) 
  2. Valery Silaev Post author
    In JavaScript 1.6 Mozilla added so called generic Array (and String) methods. Well, many of them were available previously (and not only for Mozilla browsers)… but you had to refer them in verbose way… or if you smart enough in less-verbose but “inneficient”…

    Does it make any sense for you? 😉

    Valery

    (0) 
    1. Eddy De Clercq
      Btw the solution is
      b=[];b=Array.slice(a);
      The idea behind this is that many array methods can  be applied to all objects which look like arrays, in other words with a length property.
      It fails though when one tries to apply the method directly on the object, thus the above solution should be used.
      (0) 
      1. Pran Bhas
        Array.prototype.slice.call(a)

        for IE 6.0 and Javascript<1.6

        Judging from the doco it needs a length property.It also seems the value needs to match the number of properties available for that object(a bit non generic). Nevertheless a good insight

        (0) 
        1. Valery Silaev Post author
          Pran & Eddy!

          You puzzling me on your own whith question who wins 😉
          1. Eddy’s solution is correct and really short (however, it’s JS 1.6)
          Array.slice(arrayLike);
          2. Pran’s solution is correct and interoperable
          Array.prototype.slice.call(arrayLike);

          I guess Craig have enough bonus points for you both 😉

          The latest question: how to shorten Array.prototype.slice?

          Valery

          (0) 
          1. Community User
            You gotta ping me with the winners Valery – I’m over here getting new features added to the blogs I didn’t see this 🙂

            Both have points – good job!!

            (0) 
  3. Valery Silaev Post author

    Ok, here is an answer for quiz question:<br/>1. JS version < 1.6: var b = [].slice.call(a) // expression is 16 characters<br/>2. JS version 1.6: var b = Array.slice(a) // expression is 14 characters<br/><br/>The second part of answer was submitted by Eddy De Clercq. Strictly, first part is almost the same that Pran Bhas suggested:<br/><br/>Array.prototype.slice.call(a);<br/><br/>So, Craig, these guys both deserve points 😉 <br/><br/>Seems, that it is really hard to get prototype-based OOP right 😉

    (0) 

Leave a Reply