Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Array.from and Array.of

Array.from() & Array.of()

Original discussion began on Twitter and can be found by starting here and here. Discussion was continued on es-discuss mailing list in the thread Pure win: Array.from and Array.of

Update Nov. 3, 2011

Official strawman has been posted: http://wiki.ecmascript.org/doku.php?id=strawman:array_extras

Array.from( arg ) [ Unary ]

Converts a single argument that is an array-like object or list (eg. arguments, NodeList, DOMTokenList (used by classList), NamedNodeMap (used by attributes property)) into a new Array() and returns it;

  1. Let O be the result of calling ToObject( arg ).
  2. Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length".
  3. Let len be ToUint32( lenValue ).
  4. Let A be a new array created as if by the expression new Array() where Array is the standard built-in constructor with that name.
  5. Let k be 0.
  6. Repeat, while k < len
    1. Let Pk be ToString( k ).
    2. Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
    3. If kPresent is true, then
      1. Let kValue be the result of calling the [[Get]] internal method of O with argument Pk.
      2. Call the [[DefineOwnProperty]] internal method of A with arguments ToString( k ), Property Descriptor {[[Value]]: kValue, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
    4. Increase k by 1.
  7. return A.

Implementation (Conceptual)

Array.from = function( arg ) {

  var O = Object( arg );
  var len = O.length >>> 0;
  var A = new Array();
  var k = 0;

  while( k < len ) {

    var kValue;

    if ( k in O ) {
      kValue = O[ k ];

      A[ k ] = kValue;
    }

    k++;
  }

  return A;
};

Usage

The following use cases are based on the markup in the the attached file domjunk.html.

var divs = document.querySelectorAll("div");

Array.from( divs );
// [ <div class=​"some classes" data-info=​"12">​</div>​, <div data-info=​"10">​</div>​ ]


Array.from( divs ).forEach(function( node ) {
    console.log( node );    
});
// <div class=​"some classes" data-info=​"12">​</div>​
// <div data-info=​"10">​</div>​


Array.from( divs ).filter(function( node ) {
    return !!node.classList.length;
});
// [ <div class="some classes" data-info="12"></div> ]


Array.from( divs ).reduce(function( prev, current ) {
    return ( +prev.dataset.info ) + ( +current.dataset.info );
});
// 22


Array.from( divs[0].classList )
// ["some", "classes"]


// Now shorter then [].foo.call :)
var a = Array;


a.from( divs );
// [ <div class=​"some classes" data-info=​"12">​</div>​, <div data-info=​"10">​</div>​ ]

Array.of() [ Variable Arity ]

Array.of provides a constructor that, unlike Array, does not have the special case for new Array(42), which presets length (and hints to implementations to preallocate) but leaves holes in [0, length ).

The use-case is when you can't write a literal, because you are passing a function-that-constructs as a funarg, and the eventual caller may pass only one number arg, or several args. In that case, Array will not do the right thing in the one-number-arg case. Explanation by Brendan Eich

Implementation (Conceptual)

// Harmony/ES.next rest params
Array.of = function( ...args ) { 
  return args; 
};

// Capable today
Array.of = function() { 
  return Array.prototype.slice.call( arguments ); 
};

Usage

Array.of( 10 );
// [ 10 ]

// Versus the existing behaviour of new Array():

new Array( 10 );
// [ , , , , , , , , , , ]

// Makes sense when you can't use a literal, such this case...
// ES.next http://wiki.ecmascript.org/doku.php?id=harmony:rest_parameters
var o = (function( ArrayCtor, ...rest ) {
  return ArrayCtor( rest );
})( Array, 10 );

// Using Array.of:
var o = (function( Ctor, args ) {
  return Ctor( ...args );
})( Array.of, args );

// Many args...
Array.of( "things", "that", "aren't", "currently", "an", "array" );

// [ "things", "that", "aren't", "currently", "an", "array" ]





// ES-Discuss Thread:
// https://mail.mozilla.org/pipermail/es-discuss/2011-July/015831.html
// Based on discussion originating here:
// http://twitter.com/#!/littlecalculist/status/89848378682392576
// http://twitter.com/#!/littlecalculist/status/89855977838485504
// Array.from( arrayish ) [ Unary ]
// Array.from( arrayLike ) converts any array-like object
// (eg. arguments, NodeList, DOMTokenList) into a new Array() and returns it;
// Implementation
Array.from = function( arg ) {
var O = Object( arg );
var len = O.length >>> 0;
var A = new Array();
var k = 0;
while( k < len ) {
var kValue;
if ( k in O ) {
kValue = O[ k ];
A[ k ] = kValue;
}
k++;
}
return A;
};
// Usage
var divs = document.querySelectorAll("div");
console.log(
Array.from( divs )
);
// see console
Array.from( divs ).forEach(function( node ) {
console.log( node );
});
var filtered = Array.from( divs ).filter(function( node ) {
return !!node.classList.length;
});
console.log( "filtered", filtered );
// filtered [<div class="some classes" data-info="12"></div>]
var reduced = Array.from( divs ).reduce(function( prev, current ) {
return ( +prev.dataset.info ) + ( +current.dataset.info );
});
console.log( "reduced", reduced );
// reduced 22
console.log(
Array.from( divs[0].classList )
);
var a = Array;
console.log(
// Now shorter then [].foo.call :)
a.from( divs )
);
//-------------------------------------------------------------
// Array.of() [ Variable Arity ]
// Array.of provides a constructor that, unlike Array, does not have the special case for new Array(42),
// which presets length (and hints to implementations to preallocate) but leaves holes in [0, length ).
//
// The use-case is when you can't write a literal, because you are passing a function-that-constructs
// as a funarg, and the eventual caller _may_ pass only one number arg, or several args.
// In that case, Array will not do the right thing in the one-number-arg case.
//
// See also: https://mail.mozilla.org/pipermail/es-discuss/2011-July/015841.html
// Implementation
// Harmony/ES.next rest params
Array.of = function( ...args ) {
return args;
};
// Capable today
Array.of = function() {
return Array.prototype.slice.call( arguments );
};
// Usage
Array.of( 10 );
// [ 10 ]
// Versus the existing behaviour of new Array():
new Array( 10 );
// [ , , , , , , , , , , ]
// Makes sense when you can't use a literal, such this case...
// ES.next http://wiki.ecmascript.org/doku.php?id=harmony:rest_parameters
var o = (function( ArrayCtor, ...rest ) {
return ArrayCtor( rest );
})( Array, 10 );
// Other examples:
console.log(
Array.of( "things", "that", "aren't", "currently", "an", "array" )
);
// ["things", "that", "aren't", "currently", "an", "array"]
console.log(
Array.of.apply( null, [ "foo", "bar", "baz" ] )
);
// [ "foo", "bar", "baz" ]
<div class="some classes" data-info="12"></div>
<div data-info="10"></div>

dshaw commented Jul 10, 2011

One small suggestion: put the unary form Array.from first. It's the bigger hook and thus I think it will make it easier for those who didn't follow the conversation to grasp why this is important.

Owner

rwaldron commented Jul 10, 2011

Good call, updated!

@rwldrn Very cool.

polotek commented Jul 10, 2011

+1

jdalton commented Oct 28, 2011

Array#slice w/ nodelists errors in IE < 9

Owner

rwaldron commented Oct 28, 2011

Cool, thanks for the heads up on that. Any polyfills that expect to be "usable" will need to account for that.

For performance reasons. Instead of traversing all the array, why not change the prototype:

Array.from = function( arg ) {
  arg.__proto__ = [].__proto__;
  return arg;
};

Array.from(document.querySelectorAll('div')).forEach(function(a){console.log(a);});

If the user understand what proto means (pros/cont), I prefer this implementation. In mobile devices, the difference is notable.

http://jsperf.com/array-from-implementations

Owner

rwaldron commented Nov 18, 2011

@guillermo __proto__ is non standard. The semantics I've outlined give the broadest support for arguments, NodeList, DOMTokenList and typed Arrays in all browsers. The issue is that IE6 and 7 will throw exceptions if you try to borrow Array.prototype.slice() for NodeLists

@rwldrn You are right. Too much time developing interfaces for android/ios. Sorry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment