Source: polyfill/Array.js

  1. // Licensed Materials - Property of IBM
  2. //
  3. // IBM Watson Analytics
  4. //
  5. // (C) Copyright IBM Corp. 2015, 2018
  6. //
  7. // US Government Users Restricted Rights - Use, duplication or
  8. // disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  9. ( function( Array, ObjectPolyfill, NMap, NSet )
  10. {
  11. "use strict";
  12. // This module uses bitwise operators to enforce unsigned ints or perform other optimizations.
  13. /*jshint bitwise:false*/
  14. var toObject = ObjectPolyfill.toObject,
  15. isCallable = ObjectPolyfill.isCallable,
  16. ensureCallable = ObjectPolyfill.ensureCallable;
  17. // Store reference to Object.prototype.toString for convenience and to (hopefully) grab the original before modification.
  18. var toString = Object.prototype.toString;
  19. /** @lends module:barejs/polyfill.Array */
  20. var stat = {},
  21. /** @lends module:barejs/polyfill.Array# */
  22. proto = {};
  23. /**
  24. * Method that performs the actual iteration
  25. * @memberof module:barejs/polyfill.Array~
  26. * @private
  27. */
  28. function iterate( _arrayLike, _callback, _thisArg, _logic )
  29. {
  30. var asString = ( "charAt" in _arrayLike ) && ( "substr" in _arrayLike );
  31. for ( var i = 0, len = _arrayLike.length >>> 0, value, result; i < len; ++i )
  32. {
  33. if ( asString || ( i in _arrayLike ) )
  34. {
  35. result = _callback.call( _thisArg, value = asString ? _arrayLike.charAt( i ) : _arrayLike[i], i, _arrayLike );
  36. if ( _logic( value, i, result ) === true )
  37. break;
  38. }
  39. }
  40. }
  41. /**
  42. * Polyfills for {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array Array}.
  43. * @class module:barejs/polyfill.Array
  44. */
  45. /*istanbul ignore else: We test with __ES__ set to 3*/
  46. if ( __ES__ < 5 )
  47. {
  48. /**
  49. * Enumerate all values in the array
  50. * @this {Array}
  51. * @param {function} _callback The callback to call for each value
  52. * @param {object} [_thisArg] Optional: the context in which the callback should be invoked
  53. */
  54. proto.forEach = function forEach( _callback/*, _thisArg (method has length 1)*/ )
  55. {
  56. iterate( toObject( this, forEach ), ensureCallable( _callback ), arguments[1], function() {/*no logic*/} );
  57. };
  58. /**
  59. * Check if callback returns true for every element
  60. * @this {Array}
  61. * @param {function} _callback The callback to test each value
  62. * @param {object} [_thisArg] Optional: the context in which the callback should be invoked
  63. * @returns {boolean} True if the callback returns true for each element, false otherwise.
  64. */
  65. proto.every = function every( _callback/*, _thisArg (method has length 1)*/ )
  66. {
  67. var result = true;
  68. iterate( toObject( this, every ), ensureCallable( _callback ), arguments[1], function( _v, _i, _r )
  69. {
  70. // If the callback returned a falsey value
  71. if ( !_r )
  72. {
  73. // The every statement doesn't hold true
  74. result = false;
  75. // Break execution
  76. return true;
  77. }
  78. } );
  79. return result;
  80. };
  81. /**
  82. * Check if callback returns true for any element
  83. * @this {Array}
  84. * @param {function} _callback The callback to test each value
  85. * @param {object} [_thisArg] Optional: the context in which the callback should be invoked
  86. * @returns {boolean} True if the callback returns true for at least one element, false otherwise.
  87. */
  88. proto.some = function some( _callback/*, _thisArg (method has length 1)*/ )
  89. {
  90. var result = false;
  91. iterate( toObject( this, some ), ensureCallable( _callback ), arguments[1], function( _v, _i, _r )
  92. {
  93. // If the callback returned a thruthy value, the some statement is true (shortcuted to return true for breaking)
  94. if ( _r )
  95. return ( result = true );
  96. } );
  97. return result;
  98. };
  99. /**
  100. * Creates a new array with only the elements matching the provided function.
  101. * @this {Array}
  102. * @param {function} _callback The callback to test each value.
  103. * @param {object} [_thisArg] Optional: the context in which the callback should be invoked
  104. * @returns {Array} A new array containing the result of callback per element.
  105. */
  106. proto.filter = function filter( _callback/*, _thisArg (method has length 1)*/ )
  107. {
  108. var result = [];
  109. iterate( toObject( this, filter ), ensureCallable( _callback ), arguments[1], function( _v, _i, _r )
  110. {
  111. // If the callback returned a thruthy value, add the value to the result
  112. if ( _r )
  113. result.push( _v );
  114. } );
  115. return result;
  116. };
  117. /**
  118. * Creates a new array with the results of calling a provided function on every element in this array.
  119. * @this {Array}
  120. * @param {function} _callback The callback to test each value
  121. * @param {object} [_thisArg] Optional: the context in which the callback should be invoked
  122. * @returns {Array} A new array containing the result of callback per element.
  123. */
  124. proto.map = function map( _callback/*, _thisArg (method has length 1)*/ )
  125. {
  126. var o = toObject( this, map ), result = new Array( o.length >>> 0 );
  127. iterate( o, ensureCallable( _callback ), arguments[1], function( _v, _i, _r )
  128. {
  129. result[_i] = _r;
  130. } );
  131. return result;
  132. };
  133. /**
  134. * Returns the first index at which a given element can be found in the array, or -1 if it is not present.
  135. * @this {Array}
  136. * @param {object} _searchElement Element to locate in the array.
  137. * @param {number} [_fromIndex=0] Optional: The index to start the search at. Default: 0
  138. * If the index is greater than or equal to the array's length, -1 is returned, which means
  139. * the array will not be searched. If the provided index value is a negative number, it is
  140. * taken as the offset from the end of the array. Note: if the provided index is negative,
  141. * the array is still searched from front to back. If the calculated index is less than 0,
  142. * then the whole array will be searched.
  143. * @returns {number} The first index at which a given element can be found in the array, or -1 if it is not present.
  144. */
  145. proto.indexOf = function indexOf( _searchElement/*, _fromIndex*/ )
  146. {
  147. var t = toObject( this, indexOf ), len = t.length >>> 0, i = 0;
  148. if ( len < 1 )
  149. return -1;
  150. if ( arguments.length >= 2 )
  151. {
  152. if ( ( i = arguments[1] >> 0 ) < 0 )
  153. i = Math.max( 0, len + i );
  154. }
  155. for ( ; i < len; ++i )
  156. if ( ( i in t ) && ( t[i] === _searchElement ) )
  157. return i;
  158. return -1;
  159. };
  160. /**
  161. * Returns the last index at which a given element can be found in the array, or -1 if it is not present.
  162. * The array is searched backwards, starting at fromIndex.
  163. * @this {Array}
  164. * @param {object} _searchElement Element to locate in the array.
  165. * @param {number} [_fromIndex=-1] Optional: The index at which to start searching backwards.
  166. * Defaults to the array's length - 1, i.e. the whole array will be searched. If the index is
  167. * greater than or equal to the length of the array, the whole array will be searched.
  168. * If negative, it is taken as the offset from the end of the array. Note that even when
  169. * the index is negative, the array is still searched from back to front. If the calculated
  170. * index is less than 0, -1 is returned, i.e. the array will not be searched.
  171. * @returns {number} The last index at which a given element can be found in the array, or -1 if it is not present.
  172. */
  173. proto.lastIndexOf = function lastIndexOf( _searchElement/*, _fromIndex*/ )
  174. {
  175. var t = toObject( this, lastIndexOf ), len = t.length >>> 0, i = len - 1;
  176. if ( len < 1 )
  177. return -1;
  178. if ( arguments.length >= 2 )
  179. {
  180. if ( ( i = Math.min( i, arguments[1] >> 0 ) ) < 0 )
  181. i += len;
  182. }
  183. for ( ; i >= 0; --i )
  184. if ( ( i in this ) && ( this[i] === _searchElement ) )
  185. return i;
  186. return -1;
  187. };
  188. /**
  189. * The reduce() method applies a function against an accumulator and each value of the
  190. * array (from left-to-right) has to reduce it to a single value.
  191. * @this {Array}
  192. * @param {function} _callback The callback to call for each value, taking 4 arguments:
  193. * previousValue
  194. * The value previously returned in the last invocation of the callback, or initialValue, if supplied. (See below.)
  195. * currentValue
  196. * The current element being processed in the array.
  197. * index
  198. * The index of the current element being processed in the array.
  199. * array
  200. * The array reduce was called upon.
  201. * @param {object} [_initialValue] Optional: a value to pass to the first callback.
  202. */
  203. proto.reduce = function reduce( _callback/*, _initialValue*/ )
  204. {
  205. var t = toObject( this, reduce ), len = t.length >>> 0, i = ensureCallable( _callback, 0 ), value;
  206. if ( arguments.length >= 2 )
  207. {
  208. value = arguments[1];
  209. }
  210. else
  211. {
  212. while ( ( i < len ) && !( i in t ) )
  213. ++i;
  214. if ( i >= len )
  215. throw new TypeError( "Reduce of empty array with no initial value" );
  216. value = t[i++];
  217. }
  218. for (; i < len; ++i)
  219. {
  220. if ( i in t )
  221. value = _callback( value, t[i], i, t );
  222. }
  223. return value;
  224. };
  225. /**
  226. * The reduceRight() method applies a function against an accumulator and each value of the
  227. * array (from right-to-left) has to reduce it to a single value.
  228. * @this {Array}
  229. * @param {function} _callback The callback to call for each value, taking 4 arguments:
  230. * previousValue
  231. * The value previously returned in the last invocation of the callback, or initialValue, if supplied. (See below.)
  232. * currentValue
  233. * The current element being processed in the array.
  234. * index
  235. * The index of the current element being processed in the array.
  236. * array
  237. * The array reduce was called upon.
  238. * @param {object} [_initialValue] Optional: a value to pass to the first callback.
  239. */
  240. proto.reduceRight = function reduceRight( _callback/*, _initialValue*/ )
  241. {
  242. var t = toObject( this, reduceRight ), len = t.length >>> 0, i = ensureCallable( _callback, len - 1 ), value;
  243. if ( arguments.length >= 2 )
  244. {
  245. value = arguments[1];
  246. }
  247. else
  248. {
  249. while ( ( i >= 0 ) && !( i in t ) )
  250. --i;
  251. if ( i < 0 )
  252. throw new TypeError( "Reduce of empty array with no initial value" );
  253. value = t[i--];
  254. }
  255. for (; i >= 0; --i)
  256. {
  257. if ( i in t )
  258. value = _callback( value, t[i], i, t );
  259. }
  260. return value;
  261. };
  262. /**
  263. * Check if an object is an array.
  264. * @param _arg The object to check.
  265. * @returns {boolean} true if an object is an array, false if it is not.
  266. */
  267. stat.isArray = function isArray( _arg )
  268. {
  269. return toString.call( _arg ) === "[object Array]";
  270. };
  271. // End of ES5 polyfill scope
  272. }
  273. /*istanbul ignore else: We test with __ES__ set to 3 */
  274. if ( __ES__ < 6 )
  275. {
  276. /**
  277. * Find a value in the array
  278. * @param {function} _callback The callback to test each value in the array. If the value matches, it should return true.
  279. * @param {object} [_thisArg] Optional: the context in which the callback should be invoked
  280. * @returns the found value or undefined if not found.
  281. */
  282. proto.find = function find( _callback/*, _thisArg (method has length 1)*/ )
  283. {
  284. var result = void undefined;
  285. iterate( toObject( this, find ), ensureCallable( _callback ), arguments[1], function( _v, _i, _r )
  286. {
  287. // If the callback returned a thruthy value, the result is found
  288. if ( _r )
  289. {
  290. result = _v;
  291. // Break the loop
  292. return true;
  293. }
  294. } );
  295. return result;
  296. };
  297. /**
  298. * Find a value in the array
  299. * @param {function} _callback The callback to test each value in the array. If the value matches, it should return true.
  300. * @param {object} [_thisArg] Optional: the context in which the callback should be invoked
  301. * @returns {number} the found index or -1 if not found.
  302. */
  303. proto.findIndex = function findIndex( _callback/*, _thisArg (method has length 1)*/ )
  304. {
  305. var result = -1;
  306. iterate( toObject( this, findIndex ), ensureCallable( _callback ), arguments[1], function( _v, _i, _r )
  307. {
  308. // If the callback returned a thruthy value, the result is found
  309. if ( _r )
  310. {
  311. result = _i;
  312. // Break the loop
  313. return true;
  314. }
  315. } );
  316. return result;
  317. };
  318. /**
  319. * The fill() method fills all the elements of an array from a start index to an end index with a static value.
  320. * @param _value The value to set to each index
  321. * @param {number} [_start=0] Optional: the index to start filling (inclusive)
  322. * If _start is negative, it is treated as length + _start.
  323. * @param {number} [_end] Optional: the index at which to stop filling (exclusive)
  324. * If _end is negative, it is treated as length + _end.
  325. */
  326. proto.fill = function fill( _value/*, _start, _end*/ )
  327. {
  328. var t = toObject( this, fill ), len = t.length >>> 0, i = arguments[1] >> 0, end = arguments[2];
  329. if ( i < 0 )
  330. i = Math.max( 0, i + len );
  331. if ( end === undefined )
  332. end = len;
  333. else if ( end < 0 )
  334. end = Math.max( 0, end + len );
  335. for ( ; i < end; ++i )
  336. t[i] = _value;
  337. return t;
  338. };
  339. /**
  340. * The Array.of() method creates a new Array instance with a variable number of arguments,
  341. * regardless of number or type of the arguments.
  342. * @param {...any} _value Any number of values that will be the content of the array.
  343. * @returns {Array} The created Array.
  344. */
  345. stat.of = function of()
  346. {
  347. var C = this;
  348. var args = arguments;
  349. var len = args.length;
  350. var result = isCallable( C ) ? Object( new C( len ) ) : new Array( len );
  351. for ( var i = 0; i < len; ++i )
  352. result[i] = args[i];
  353. return result;
  354. };
  355. /**
  356. * The Array.from() method creates a new Array instance from an array-like or iterable object.
  357. * @param {object} _arrayLike An array-like or iterable object to convert to an array.
  358. * @param {function} [_mapFn] Optional. Map function to call on every element of the array.
  359. * @param {object} [_thisArg] Optional. Value to use as this when executing mapFn.
  360. * @returns {Array} The created Array.
  361. */
  362. stat.from = function from( _arrayLike/*, _mapFn, _thisArg*/ )
  363. {
  364. var C = this;
  365. var items = toObject( _arrayLike );
  366. var mapFn = typeof arguments[1] !== "undefined" ? ensureCallable( arguments[1] ) : null;
  367. var thisArg = arguments[2];
  368. // Iterators can't be emulated, but add specific logic for Map and Set so those are supported
  369. // Note: check both for a potential global and the polyfill separately:
  370. // NMap and Map MAY point to the same function, but don't have to in every environment (!).
  371. var isMap = items instanceof NMap;
  372. // Normalize to an Array, source. Chances are high we can just return this Array, or map it in place.
  373. var source;
  374. var i, len;
  375. if ( mapFn )
  376. {
  377. if ( thisArg === undefined )
  378. thisArg = null;
  379. else if ( thisArg !== null )
  380. thisArg = Object( thisArg );
  381. }
  382. var it = isCallable( items.next ) ? items : ObjectPolyfill.getIterator( items );
  383. if ( it )
  384. {
  385. source = [];
  386. for ( var cur = it.next(); !cur.done; cur = it.next() )
  387. source.push( cur.value );
  388. len = source.length;
  389. }
  390. // IE11's native map and set support forEach, but not iterators. Emulate using forEach.
  391. else if ( ( isMap || ( items instanceof NSet ) ) && isCallable( items.forEach ) )
  392. {
  393. i = 0;
  394. len = Math.floor( items.size ) || 0;
  395. source = new Array( len );
  396. items.forEach( function( _value, _key )
  397. {
  398. var entry;
  399. if ( isMap )
  400. {
  401. entry = new Array( 2 );
  402. entry[0] = _key;
  403. entry[1] = _value;
  404. }
  405. else
  406. {
  407. entry = _value;
  408. }
  409. source[ i++ ] = entry;
  410. } );
  411. }
  412. if ( !source )
  413. {
  414. var asString = ( "charAt" in items ) && ( "substr" in items );
  415. len = Math.floor( items.length ) || 0;
  416. source = new Array( len );
  417. for ( i = 0; i < len; ++i )
  418. source[i] = asString ? items.charAt( i ) : items[i];
  419. }
  420. // We already have an Array (source), only create a new Object if we have a Constructor as context
  421. var result = isCallable( C ) && C !== Array ? Object( new C( len ) ) : source;
  422. if ( mapFn || result !== source )
  423. {
  424. for ( i = 0; i < len; ++i )
  425. result[i] = mapFn ? mapFn.call( thisArg, source[i], i ) : source[i];
  426. }
  427. return result;
  428. };
  429. // End of ES6 polyfill scope
  430. }
  431. /*istanbul ignore else: We test with __ES__ set to 3*/
  432. if ( __ES__ < 7 )
  433. {
  434. /**
  435. * The includes() method determines whether an array includes a certain element, returning true or false as appropriate.
  436. * The array is searched forwards, starting at fromIndex (defaults to 0).
  437. * @param {object} _searchElement Element to locate in the array.
  438. * @param {object} [_fromIndex=0] Optional: The index to start the search at. Default: 0
  439. * If the index is greater than or equal to the array's length, -1 is returned, which means
  440. * the array will not be searched. If the provided index value is a negative number, it is
  441. * taken as the offset from the end of the array. Note: if the provided index is negative,
  442. * the array is still searched from front to back. If the calculated index is less than 0,
  443. * then the whole array will be searched.
  444. * @returns {boolean} True if the element was found, false otherwise.
  445. */
  446. proto.includes = function includes( _searchElement/*, _fromIndex*/ )
  447. {
  448. var t = toObject( this, includes ), len = t.length >>> 0, i = 0;
  449. if ( len < 1 )
  450. return false;
  451. if ( arguments.length > 1 )
  452. {
  453. if ( ( i = arguments[1] >> 0 ) < 0 )
  454. i = Math.max( 0, len + i );
  455. }
  456. for ( ; i < len; ++i )
  457. if ( ( t[i] === _searchElement ) || ( _searchElement !== _searchElement && t[i] !== t[i] ) )
  458. return true;
  459. return false;
  460. };
  461. // End of ES7 polyfill scope
  462. }
  463. ObjectPolyfill.polyfill( Array, stat, proto, exports, "Array" );
  464. // End of module
  465. }( Array, require( "./Object" ), require( "../NMap" ), require( "../NSet" ) ) );