CUTCODEDOWN
Minimalist Semantic Markup

Welcome Guest
Please Login or Register

If you have registered but not recieved your activation e-mail in a reasonable amount of time, or have issues with using the registration form, please use our Contact Form for assistance. Include both your username and the e-mail you tried to register with.

Author Topic: JavaScript: building a better Array.from  (Read 484 times)

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 919
  • Karma: +171/-1
    • CutCodeDown -- Minimalist Semantic Markup
JavaScript: building a better Array.from
« on: 10 May 2020, 06:40:55 am »
One problem I'm constantly butting heads with is how most array-like iterables don't have the Array object methods on them.  Array.from is supposed to create a new array from iterables, but it doesn't work on a lot of objects. More specifically any non-numeric but iterable property is omitted from the resultant array.

I was working with the polyfill off MDN for Array.from in adding it to the next version of my elementals library, when it hit me that I could not only simplify the whole process greatly... I could make it work on all iterable object elements. This is what I ended up with.

Code: [Select]
Object.defineProperty(Array, '__from', {
value : function(arrayLike, mapFn, thisArg) {
if (null == arrayLike) {
console.trace();
throw new TypeError('Array.__from : "arrayLike" null or undefined');
}
arrayLike = Object(arrayLike);
var
result = [],
j = 0;
if (!mapFn) {
for (var i in arrayLike) result[j++] = arrayLike[i];
return result;
}
if (
'function' === typeof mapFn ||
'[object Function]' === Object.toString.call(mapFn)
) {
if ('undefined' === typeof thisArg) thisArg = null;
for (var i in arrayLike) result[j] = mapFn.call(thisArg, arrayLike[i], j++);
return result;
}
console.trace();
throw new TypeError('Array.__from : When provided "mapFn" must be a function');
}
}); // Array.__from

I called it __from as I use double underscores as a prefix to indicate non-standard methods when I attach them to system objects. Object.defineProperty is used so that this method cannot be overwritten/changed, throwing an error should a conflict arise with some other library/framework/whatHaveYou.

First we run a check against null/undefined so that it behaves just like the offical Array object methods. I actually have a method for this in my library -- _.Throw.isNull -- but I expanded it out here for clarity.

I typecast the arrayLike to object so it's "in" iterable, this makes all array-likes behave in a uniform fashion. So many polyfills will create new variables when there's no reason for it. Just re-use the parameters. You CAN change their values and even when it's an Object passed by reference, re-assignment does not wipe the original! That's because you're changing the REFERENCE, not what it's referring TO!

The result and a second depth counter are set up as empty and zero respectively. Do this one cince most execution cases use both variables.

If there's no mapping function, we just iterate through the arraylike and copy the values to the result, returning it.

Otherwise if the mapping function is actually a function (or method) we need to use the result of that map function. We check thisArg to see if it is defined, if not we need to make sure it is NULL for using the Function.call method. Loop as before, but MapFn.call. We can increment j inside the call, meaning we don't need to do so separately.

As to this point everything has done a return, we don't need an else. It gets this far mapFn wasn't a function, which is invalid, so throw an appropriate typeError.

Note that when I throw a type error I also provide a console backtrace. Not all browsers do so when a TypeError is issued, and it aids in debugging.

Dunno if anyone else will find it useful, but hey, figured I'd toss this one out there.

Also I find it odd how many polyfills do all their grunt-work inside the loop instead of before it. /FAIL/ at basic programming logic guys!

If you have: (lifted straight out of the MDN polyfill)

Code: [Select]
      // 16. Let k be 0.
      var k = 0;
      // 17. Repeat, while k < len… (also steps a - h)
      var kValue;
      while (k < len) {
        kValue = items[k];
        if (mapFn) {
          A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
        } else {
          A[k] = kValue;
        }
        k += 1;
      }

You have unnecessary assignment AND an if statement inside the loop, slowing down the routine. One of the first bloody things I was taught in programming was NOT to do that!

Code: [Select]
if (mapFn) {
if ('undefined' === typeof T) T = null;
for (var k = 0; k < len; k++) A[k] = mapFn.call(T, items[k], k);
} else {
for (var k = 0; k < len; k++) A[k] = items[k];
}

Does the same thing in a fraction the execution time because we removed some variables for nothing, moved logic that only needs to be run once out of the loop, removed the normal vs .call approach by simply using null as the "this"...

Do NOT repeat the same conditions / comparisons that have the same result every blasted time INSIDE the loop! Programming 101!
« Last Edit: 10 May 2020, 06:53:05 am by Jason Knight »
I'll fix every flaw, I'll break every law, I'll tear up the rulebook if that's what it takes. You will see, I will crush this cold machine.

 

SMF spam blocked by CleanTalk

Advertisement