Skip to Content
Author's profile photo Former Member

Quiz series: Micro JavaScript / How _not_ to copy arrays

[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!

Assigned Tags

      22 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Former Member
      Former Member
      25 Bonus points to the one Valery declares winner of the mini quiz (on the last one to Valery).

      And keep them coming!

      Author's profile photo Former Member
      Former Member
      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

      Author's profile photo Former Member
      Former Member

      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

      Author's profile photo Former Member
      Former Member
      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

      Author's profile photo Dagfinn Parnas
      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)

      Author's profile photo Dagfinn Parnas
      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

      Author's profile photo Dagfinn Parnas
      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)

      Author's profile photo Former Member
      Former Member
      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

      Author's profile photo Dagfinn Parnas
      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.
      Author's profile photo Former Member
      Former Member
      Dagfinn,

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

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

      Valery

      Author's profile photo Dagfinn Parnas
      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 πŸ™‚

      Author's profile photo Former Member
      Former Member
      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..

      Author's profile photo Former Member
      Former Member
      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

      Author's profile photo Eddy De Clercq
      Eddy De Clercq
      Mine is shorter though: 26:-) With a loop.
      Author's profile photo Eddy De Clercq
      Eddy De Clercq
      My "loop-less" solution counts 46
      Author's profile photo Former Member
      Former Member
      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

      Author's profile photo Eddy De Clercq
      Eddy De Clercq
      It takes only 18 chars and is in fact indeed real simple once you're on the right track.
      Author's profile photo Eddy De Clercq
      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.
      Author's profile photo Former Member
      Former Member
      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

      Author's profile photo Former Member
      Former Member
      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

      Author's profile photo Former Member
      Former Member
      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!!

      Author's profile photo Former Member
      Former Member

      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 πŸ˜‰