PageRenderTime 64ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/com/alanmacdougall/underscore/_.as

https://github.com/mikevalenty/underscore.as
ActionScript | 932 lines | 507 code | 80 blank | 345 comment | 140 complexity | b242fe512e1bdab09a9e4a69ce1ad954 MD5 | raw file
  1. /**
  2. * underscore.as, by Alan MacDougall [http://www.github.com/amacdougall/]
  3. *
  4. * Ported from underscore.js, (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
  5. * See http://http://documentcloud.github.com/underscore/
  6. *
  7. * Like underscore.js, it is freely distributable under the MIT license.
  8. */
  9. package com.alanmacdougall.underscore {
  10. // imports
  11. import flash.utils.ByteArray;
  12. import flash.utils.Dictionary;
  13. import flash.utils.Timer;
  14. import flash.events.TimerEvent;
  15. /**
  16. * An AS3 adaptation of underscore.js. Note that most collection methods accept
  17. * Array, Object, or XMLList collections, but will return only Arrays. Use
  18. * Underscore to further manipulate a data set from XML after getting an
  19. * XMLList via E4X. Consider using _.toArray on an XMLList if you need access
  20. * to a function that doesn't support XMLLists.
  21. *
  22. * Default iterator format: function(value:*, index:*?, list:*? {}
  23. */
  24. public var _:* = (function():Function {
  25. var breaker:Object = {};
  26. var _:* = function(obj:*):* {
  27. return new Wrapper(obj);
  28. };
  29. /* This hack is necessary to work around AS3's argument count
  30. * enforcement and lack of Function.arity. We want the user to
  31. * be able to use function(item), function(item, index), or
  32. * function(item, index, context) as the whim strikes them,
  33. * without having to declare function(...args) and figure out
  34. * the indexes themselves, or provide default values for an
  35. * argument they won't use often, like context.
  36. *
  37. * So instead, we just try the most common case first and then
  38. * try each subsequent case when we get ArgumentErrors. Trying
  39. * one arg first means the fewest exceptions.
  40. */
  41. var safeCall:Function = function(iterator:Function, context:Object, ...args):* {
  42. var answer:*;
  43. var additionalArgs:Array = [];
  44. while (args.length > 1) {
  45. additionalArgs.push(args.pop());
  46. }
  47. while (true) {
  48. try {
  49. answer = iterator.apply(context, args);
  50. } catch (e:ArgumentError) {
  51. if (additionalArgs.length == 0) {
  52. // if we've run out of additional args, give up
  53. throw e;
  54. }
  55. args.push(additionalArgs.pop());
  56. continue;
  57. }
  58. break;
  59. }
  60. return answer;
  61. }
  62. /* COLLECTIONS */
  63. /**
  64. * Executes the iterator on each element of the input collection. Works on
  65. * Array, Object, or XMLList collections.
  66. *
  67. * @throws ArgumentError if obj is not a collection.
  68. */
  69. var each:Function = _.each = function(obj:*, iterator:Function, context:Object = null):* {
  70. var i:int;
  71. if (obj is Array) {
  72. // TO DO: benchmark native Array.forEach
  73. for (i = 0; i < obj.length; i++) {
  74. if (safeCall(iterator, context, obj[i], i, obj) === breaker) return;
  75. }
  76. } else if (obj is Object) {
  77. for (var key:String in obj) {
  78. if (obj.hasOwnProperty(key)) {
  79. if (safeCall(iterator, context, obj[key], key, obj) === breaker) return;
  80. }
  81. }
  82. } else if (obj is XMLList) {
  83. for (i = 0; i < obj.length(); i++) {
  84. if (safeCall(iterator, context, obj[i], i, obj) == breaker) return;
  85. }
  86. } else {
  87. throw new Error("Attempted to iterate over incompatible object " +
  88. obj + "; must iterate over an Array, an Object, or an XMLList.");
  89. }
  90. };
  91. /** Returns the input items, transformed by the iterator, as an Array. */
  92. var map:Function = _.map = function(obj:*, iterator:Function, context:Object = null):Array {
  93. var results:Array = [];
  94. if (obj == null) return results;
  95. // TO DO: benchmark native Array.map
  96. each(obj, function(value:*, index:*, list:* = null):* {
  97. results.push(safeCall(iterator, context, value, index, list));
  98. });
  99. return results;
  100. };
  101. /**
  102. * Returns the input items, filtered by the iterator, as an Array. Aliased
  103. * as _.select.
  104. */
  105. _.filter = _.select = function(obj:*, iterator:Function, context:Object = null):Array {
  106. var results:Array = [];
  107. if (obj == null) return results;
  108. each(obj, function(value:*, index:* = null, list:* = null):* {
  109. safeCall(iterator, context, value, index, list) && results.push(value);
  110. });
  111. return results;
  112. };
  113. /**
  114. * Returns the input items, rejecting any which pass the truth test.
  115. */
  116. _.reject = function(obj:*, iterator:Function, context:Object = null):Array {
  117. var results:Array = [];
  118. if (obj == null) return results;
  119. each(obj, function(value:*, index:* = null, list:* = null):* {
  120. safeCall(iterator, context, value, index, list) || results.push(value);
  121. });
  122. return results;
  123. };
  124. /**
  125. * Executes the iterator on each collection element. The iterator has
  126. * access to a memo variable which can be modified by each iteration in
  127. * turn, to build a string or sum a mathematical series (for example). At
  128. * the end of the iteration, the memo is returned. If no memo is supplied,
  129. * the first value in the list is used as the memo. As in the original
  130. * underscore.js implementation, if no memo is supplied, the iterator is
  131. * not run on the first value of the list.
  132. *
  133. * Simply returns the memo if run on an empty collection.
  134. *
  135. * Iterator format: function(memo:*, value:*, index:*?, list:*?):* {}
  136. */
  137. _.reduce = _.foldl = _.inject = function(obj:*, iterator:Function, memo:* = null, context:Object = null):* {
  138. if (_.isEmpty(obj)) return memo;
  139. var memoInitialized:Boolean = (memo != null);
  140. each(obj, function(value:*, index:*, list:*):* {
  141. if (!memoInitialized) {
  142. memo = value;
  143. memoInitialized = true;
  144. } else {
  145. memo = safeCall(iterator, context, memo, value, index, list);
  146. }
  147. });
  148. return memo;
  149. }
  150. // TO DO: implement _.reduceRight
  151. /**
  152. * Returns the first collection element for which the iterator returns
  153. * true. If no element qualifies, returns null. Assigning null to a typed
  154. * variable whose type is not nullable, such as an int, will coarse the
  155. * value to 0 instead of null, so watch out.
  156. */
  157. _.detect = _.find = function(obj:*, iterator:*, context:Object = null):* {
  158. var result:* = null;
  159. any(obj, function(value:*, index:* = null, list:* = null):Boolean {
  160. if (safeCall(iterator, context, value, index, list)) {
  161. result = value;
  162. return true;
  163. }
  164. return false;
  165. });
  166. return result;
  167. };
  168. /**
  169. * True if the iterator returns true for any collection element. If the
  170. * iterator is omitted, tests the elements themselves for truthiness.
  171. */
  172. var any:Function = _.any = _.some = function(obj:*, iterator:Function = null, context:Object = null):Boolean {
  173. if (obj == null || _(obj).isEmpty()) return false;
  174. // TO DO: benchmark native Array.some
  175. iterator = iterator || identity;
  176. var result:Boolean = false;
  177. each(obj, function(value:*, index:* = null, list:* = null):* {
  178. result = safeCall(iterator, context, value, index, list);
  179. if (result) return breaker; // stop on first true value
  180. });
  181. return result;
  182. };
  183. /**
  184. * True if the iterator returns true for all collection elements. If the
  185. * iterator is omitted, tests the elements themselves for truthiness.
  186. */
  187. _.all = function(obj:*, iterator:Function = null, context:Object = null):Boolean {
  188. if (obj == null) return false;
  189. // TO DO: benchmark native Array.every
  190. iterator = iterator || identity;
  191. var result:Boolean = true;
  192. each(obj, function(value:*, index:* = null, list:* = null):* {
  193. result = safeCall(iterator, context, value, index, list);
  194. if (!result) return breaker; // stop on first false value
  195. });
  196. return result;
  197. }
  198. /**
  199. * True if the target is present in the collection. Uses strict
  200. * (threequals) equality. Named "include" in underscore.js, but since
  201. * that's a special directive in AS3, it causes an error.
  202. */
  203. _.includes = _.contains = function(obj:*, target:*):Boolean {
  204. if (obj is XMLList) throw new ArgumentError("_.includes cannot operate on an XMLList.");
  205. if (obj == null || _(obj).isEmpty()) return false;
  206. return any(obj, function(element:*):Boolean {
  207. return element === target;
  208. });
  209. }
  210. /**
  211. * Invoke the named method on each collection element. If the method name
  212. * is omitted, and the element is a function, invokes the element. Any
  213. * additional arguments will be passed as parameters to each function call.
  214. * To call a list of functions with params, _.invoke(list, null, arg1,
  215. * ...).
  216. *
  217. * @return An Array of the results of each invocation, or null if all
  218. * functions were void or returned null.
  219. */
  220. _.invoke = function(obj:*, functionName:String = null, ...args):Array {
  221. // TO DO: compact the result
  222. return map(obj, function(element:*):* {
  223. return (functionName ? element[functionName] : element).apply(element, args);
  224. });
  225. }
  226. /**
  227. * Operates on a collection of Objects, returning an array of the values of
  228. * the named property. Example:
  229. *
  230. * var hashes:Array = [{name: "foo"}, {name: "bar"}];
  231. * _(hashes).pluck("name"); // ["foo", "bar"]
  232. *
  233. * @throws ArgumentError if called on an XMLList, since XML nodes have no
  234. * single key. Could be @id, tag name, or who knows what. Use _.map!
  235. */
  236. _.pluck = function(obj:*, key:String):Array {
  237. if (obj is XMLList) throw new ArgumentError("_.pluck cannot operate on an XMLList.");
  238. return map(obj, function(element:Object):* {
  239. return element[key];
  240. });
  241. }
  242. /**
  243. * Returns the maximum value in the collection. If an iterator is passed,
  244. * it must return a numeric value for each element. Otherwise the element
  245. * itself will be compared using gt/lt operators, with undefined results if
  246. * the values cannot be compared. Special cases: Arrays will be compared by
  247. * length; Dates will be compared by their millisecond position in the Unix
  248. * epoch, i.e. Date.getTime().
  249. */
  250. _.max = function(obj:*, iterator:Function = null, context:Object = null):* {
  251. // unlike in underscore.js, "value" means numeric value, "element" is the real item
  252. var maxElement:* = null;
  253. var maxValue:Number = -Infinity;
  254. each (obj, function(element:*, index:*, list:*):void {
  255. var value:Number = iterator != null ?
  256. safeCall(iterator, context, element, index, list) :
  257. (element is Date ? element.getTime() : element);
  258. if (value >= maxValue) {
  259. maxValue = value;
  260. maxElement = element;
  261. }
  262. });
  263. return maxElement;
  264. };
  265. /**
  266. * Returns the minimum value in the collection. If an iterator is passed,
  267. * it must return a numeric value for each element. Otherwise the element
  268. * itself will be compared using gt/lt operators, with undefined results if
  269. * the values are non-numeric.
  270. */
  271. _.min = function(obj:*, iterator:Function = null, context:Object = null):* {
  272. // unlike in underscore.js, "value" means numeric value, "element" is the real item
  273. var minElement:* = null;
  274. var minValue:Number = Infinity;
  275. each (obj, function(element:*, index:*, list:*):void {
  276. var value:Number = iterator != null ?
  277. safeCall(iterator, context, element, index, list) :
  278. (element is Date ? element.getTime() : element);
  279. if (value <= minValue) {
  280. minValue = value;
  281. minElement = element;
  282. }
  283. });
  284. return minElement;
  285. };
  286. /**
  287. * Sort the objects values by running the iterator on each one. Iterator
  288. * must return a numeric value.
  289. */
  290. _.sortBy = function(obj:*, iterator:Function = null, context:Object = null):Array {
  291. // unlike in underscore.js, "value" means numeric value, "element" is the real item
  292. var results:Array = map(obj, function(element:*, index:*, list:*):Object {
  293. return {
  294. element: element,
  295. value: safeCall(iterator, context, element, index, list)
  296. };
  297. });
  298. // AS3's Array.sort mutates the array instead of returning a new sorted one.
  299. results.sort(function(left:Object, right:Object):int {
  300. if (left.value < right.value) {
  301. return -1;
  302. } else if (left.value == right.value) {
  303. return 0;
  304. } else {
  305. return 1;
  306. }
  307. });
  308. return _(results).pluck("element");
  309. };
  310. /**
  311. * Operating on a sorted array, returns the index at which the supplied
  312. * value should be inserted to main sort order. Optionally accepts an
  313. * iterator which produces a numeric value for each element; otherwise,
  314. * compares elements directly.
  315. *
  316. * Iterator format: function(element:*):Number {}
  317. */
  318. _.sortedIndex = function(list:Array, element:*, iterator:Function = null):int {
  319. iterator = iterator || identity;
  320. var low:int = 0;
  321. var high:int = list.length;
  322. while (low < high) {
  323. var mid:int = (low + high) >> 1; // i.e. Math.floor((low + high) / 2)
  324. iterator(list[mid]) < iterator(element) ? low = mid + 1 : high = mid;
  325. }
  326. return low;
  327. };
  328. /**
  329. * Transforms the collection into an Array, discarding keys if it was an
  330. * Object. If collection is already an Array, returns a shallow copy. If
  331. * collection was an XMLList, returns an array of nodes.
  332. */
  333. _.toArray = function(collection:*):Array {
  334. if (collection == null) return [];
  335. if (collection is Array) return (collection as Array).slice();
  336. var result:Array = [];
  337. for each (var element:* in collection) {
  338. result.push(element);
  339. }
  340. return result;
  341. };
  342. /** Returns the number of elements in the collection. */
  343. _.size = function(obj:*):int {
  344. return _(obj).toArray().length;
  345. }
  346. /**
  347. * True if the collection has no elements; false otherwise.
  348. *
  349. * @throws ArgumentError if obj is not an Object or Array.
  350. */
  351. _.isEmpty = function(obj:*):Boolean {
  352. if (obj is Array) {
  353. return obj.length == 0;
  354. } else if (obj is Object) {
  355. var n:int = 0;
  356. for (var key:String in obj) {n++;};
  357. return n == 0;
  358. } else {
  359. throw new ArgumentError("_.isEmpty was called with incompatible object " + obj +
  360. "; must be an Object or Array.");
  361. }
  362. };
  363. /* ARRAYS */
  364. /**
  365. * Returns the first element of the list; or if the optional n argument
  366. * is supplied, returns the first n elements of the list an an Array.
  367. */
  368. _.first = _.head = function(list:Array, n:int = -1):Object {
  369. return n == -1 ? list[0] : list.slice(0, n);
  370. };
  371. _.rest = _.tail = function(list:Array, n:int = 1):* {
  372. return list.slice(n);
  373. };
  374. /**
  375. * Returns the last element of the array. If the optional n argument
  376. * is supplied, returns an array of the last n elements of the array.
  377. */
  378. _.last = function(list:Array, n:int = -1):Object {
  379. if (n == -1) {
  380. return list[list.length - 1];
  381. } else {
  382. return list.slice(-n);
  383. }
  384. };
  385. /**
  386. * Returns an array with all false, null, undefined, NaN, or empty-string
  387. * values removed.
  388. */
  389. _.compact = function(list:Array):Array {
  390. return _(list).select(function(element:*):Boolean {
  391. return !!element; // seems to work in AS3 as well as Javascript
  392. });
  393. };
  394. /** Returns a flattened version of a nested array. */
  395. _.flatten = function(list:Array):Array {
  396. return _(list).reduce(function(accumulator:Array, element:*):Array {
  397. if (element is Array) {
  398. return accumulator.concat(_(element).flatten());
  399. }
  400. accumulator.push(element);
  401. return accumulator;
  402. }, []);
  403. };
  404. _.without = function(list:Array, ...targets):Array {
  405. return _(list).reject(function(element:*):Boolean {
  406. return _(targets).any(function(target:*):Boolean {
  407. return element === target;
  408. });
  409. });
  410. };
  411. _.unique = function(list:Array):Array {
  412. // use immediately executing function to isolate known array
  413. return (function(list:Array):Array {
  414. var known:Array = [];
  415. return _(list).select(function(element:*):Boolean {
  416. if (_(known).includes(element)) {
  417. return false;
  418. } else {
  419. known.push(element);
  420. return true;
  421. }
  422. });
  423. })(list);
  424. };
  425. /**
  426. * Returns an array of all values which are present in all supplied arrays.
  427. */
  428. _.intersection = function(list:Array, ...targets):Array {
  429. return _(list).select(function(element:*):Boolean {
  430. return _(targets).all(function(target:Array):Boolean {
  431. return _(target).includes(element);
  432. });
  433. });
  434. };
  435. /**
  436. * Returns an array of all values which are present in all supplied arrays.
  437. * An alias for _.intersection.
  438. */
  439. _.intersect = _.intersection;
  440. /** Take the difference between one array and a number of other arrays. Only the elements present in just the first array will remain. */
  441. _.difference = function(list:Array, ...others):Array {
  442. return _(list).select(function(element:*):Boolean {
  443. return _(others).all(function(other:Array):Boolean {
  444. return !_(other).includes(element);
  445. });
  446. });
  447. };
  448. /** Zips multiple arrays together. Essentially rotates a nested array 90 degrees. */
  449. _.zip = function(...args):Array {
  450. var maxRowLength:int = _(args).chain().pluck("length").max().value();
  451. var results:Array = [];
  452. for (var rowIndex:int = 0; rowIndex < maxRowLength; rowIndex++) {
  453. var column:Array = [];
  454. for (var columnIndex:int = 0; columnIndex < args.length; columnIndex++) {
  455. column.push(args[columnIndex][rowIndex]);
  456. }
  457. results.push(column);
  458. }
  459. return results;
  460. }
  461. _.range = function(start:Number, stop:Number = NaN, step:Number = NaN):Array {
  462. if (isNaN(stop)) { // _.range(10) counts 0 to 9
  463. stop = start;
  464. start = 0;
  465. }
  466. step = step || 1;
  467. var length:int = Math.max(Math.ceil((stop - start) / step), 0);
  468. var index:int = 0;
  469. var range:Array = [];
  470. while (index < length) {
  471. range.push(start);
  472. start += step;
  473. index++;
  474. }
  475. return range;
  476. }
  477. /* FUNCTIONS */
  478. /**
  479. * Binds the function to the supplied object, returning a "bound" function
  480. * whose "this" is the supplied object. Optionally curries the function
  481. * with the supplied arguments. If these terms are unfamiliar, look up the
  482. * fundamentals of scope, binding, and closures in AS3 (or Javascript,
  483. * since they work almost identically and JS resources may be more common).
  484. * And prepare to be enlightened.
  485. */
  486. _.bind = function(f:Function, obj:Object = null, ...curriedArgs):Function {
  487. // The ...rest construct will yield an empty array if no extra args are passed.
  488. return function(...invocationArgs):* {
  489. return f.apply(obj, curriedArgs.concat(invocationArgs));
  490. };
  491. };
  492. /**
  493. * Binds functions to the supplied object. If no function names are given,
  494. * binds all functions found on the object. Useful when the functions will
  495. * be called in a foreign context, such as an event listener. Use this to
  496. * simulate method binding when constructing objects at runtime.
  497. */
  498. _.bindAll = function(obj:Object, ...functionNames):Object {
  499. _(functionNames).isEmpty() && (functionNames = _(obj).functions());
  500. _(functionNames).each(function(name:String):void {
  501. obj[name] = _(obj[name]).bind(obj);
  502. });
  503. return obj;
  504. };
  505. /**
  506. * Memoizes a function by caching its results in a lookup table stored in
  507. * the closure. For example, once f(x:int) is called as f(100), the result
  508. * is cached before it is returned, and future calls as f(100) will simply
  509. * return the cached result.
  510. *
  511. * Uses a Dictionary internally instead of an Object, meaning that
  512. * non-scalar arguments will be keyed by identity. If f(s:Sprite) is
  513. * called as f(foo), future calls as f(foo) will return the cached result;
  514. * but even if sprite "bar" is precisely identical to foo, f(bar) will
  515. * force a new function call and cache a new value.
  516. *
  517. * Optionally accepts a "hasher" function which produces a hash code for a
  518. * given input value. For instance, if all input values 0-10, 11-20, etc
  519. * are expected to produce the same output, hasher could be designed to
  520. * produce a single key for each level of input:
  521. * function(n:int):String {return (Math.floor(n / 10) * 10).toString();}.
  522. *
  523. * Defining a custom hasher function is the only way to memoize a function
  524. * which takes more than one argument, or to memoize a function which
  525. * might return the same result for non-identical arguments.
  526. */
  527. _.memoize = function(f:Function, hasher:Function = null):Function {
  528. var memo:Dictionary = new Dictionary(); // lookup table mapping input to output values
  529. hasher = hasher || identity;
  530. return function(...args):* {
  531. var key:String = hasher.apply(this, args);
  532. return key in memo ? memo[key] : (memo[key] = f.apply(this, args));
  533. };
  534. };
  535. /**
  536. * Executes the function after a delay. Optional args will be passed to the
  537. * function at runtime.
  538. *
  539. * @return A Timer which can be stopped to prevent the delayed execution.
  540. */
  541. /* Although AS3 has setTimeout and setInterval, the Adobe-approved timing
  542. * method is Timer, so _.delay returns a Timer which can be stopped to
  543. * prevent the delayed function.
  544. */
  545. _.delay = function(f:Function, wait:int, ...args):Timer {
  546. var timer:Timer = new Timer(wait, 1);
  547. timer.addEventListener(TimerEvent.TIMER_COMPLETE, function(event:TimerEvent):void {
  548. f.apply(f, args);
  549. });
  550. timer.start();
  551. return timer;
  552. }
  553. /**
  554. * Executes the function after the current call stack has completed. Good
  555. * for functions which should not block execution, or to call a single
  556. * event handler after many synchronous calls. Alternative strategy: create
  557. * an event handler using _.debounce(f, 0).
  558. *
  559. * @return A Timer which can be stopped to prevent the deferred execution.
  560. */
  561. _.defer = function(f:Function, ...args):Timer {
  562. return _(f).delay(0);
  563. }
  564. /**
  565. * Internal function, but debounce and throttle can be considered
  566. * convenience methods for this.
  567. */
  568. // choke implementation suggested by Nick Schaubeck
  569. var limit:Function = function(f:Function, wait:int,
  570. debounce:Boolean = false, callThrottledImmediately:Boolean = false):Function {
  571. var timer:Timer = new Timer(wait, 1);
  572. /*
  573. These variables are defined outside the returned function, but set
  574. within it. This lets us create the throttler and set up the timer
  575. events outside the returned function, which will be called many times.
  576. */
  577. var context:Object;
  578. var args:Array;
  579. var throttler:Function = function():void {
  580. timer.stop();
  581. callThrottledImmediately || f.apply(context, args);
  582. };
  583. timer.addEventListener(TimerEvent.TIMER_COMPLETE, throttler);
  584. return function(...runtimeArgs):* {
  585. args = runtimeArgs;
  586. context = this;
  587. callThrottledImmediately && !timer.running && f.apply(context, args);
  588. debounce && timer.stop();
  589. (debounce || !timer.running) && timer.start();
  590. };
  591. };
  592. /**
  593. * Returns a wrapped function which can only execute once every so many
  594. * milliseconds. As in underscore.js, even the first call is delayed by
  595. * the wait duration; but if the optional callImmediately argument is
  596. * true, the first call occurs immediately and subsequent calls are
  597. * locked out for the wait duration.
  598. */
  599. _.throttle = function(f:Function, wait:int, callImmediately:Boolean = false):Function {
  600. return limit(f, wait, false, callImmediately);
  601. };
  602. /**
  603. * Returns a wrapped function which executes once, and then fails silently
  604. * for the given cooldown period. Equivalent to calling _.throttle with the
  605. * callImmediately argument = true.
  606. */
  607. _.choke = function(f:Function, wait:int):Function {
  608. return limit(f, wait, false, true);
  609. }
  610. /**
  611. * Returns a wrapped function which only executes the most recent call,
  612. * after the given delay. Each call resets the delay. Good for performing
  613. * an update after a sequence of rapid inputs, such as typing or dragging.
  614. */
  615. _.debounce = function(f:Function, wait:int):Function {
  616. return limit(f, wait, true);
  617. };
  618. /**
  619. * Returns a function which takes the initial function as an argument.
  620. * Useful for automatically transforming or interpreting the result of the
  621. * intial function, or logging when it is called, or something. Wrapper is
  622. * welcome to pass its arguments along to the original function, or take
  623. * completely different ones.
  624. */
  625. _.wrap = function(f:Function, wrapper:Function):Function {
  626. return function(...args):* {
  627. args.unshift(f);
  628. return wrapper.apply(this, args);
  629. }
  630. };
  631. /**
  632. * Creates a function whose return value is the result of passing the
  633. * output of each function as the sole argument of the next. The classic
  634. * example is h(x) = g(f(x)) -- the input to g() is the output of f(x).
  635. * More practically, createRedSprite = _.compose(buildSprite,
  636. * turnSpriteRed);
  637. *
  638. * While underscore.js passed values from right to left, I'm passing them
  639. * left to right. It seems more natural to me. If this is actually a
  640. * violation of some functional programming convention, let me know and
  641. * I'll change it!
  642. *
  643. * Also, in underscore.js, the first function to be called could have more
  644. * than one argument, while the others could not. This seems inconsistent
  645. * both internally and with the concept of function composition. I've made
  646. * it so each function takes exactly one input value.
  647. */
  648. _.compose = function(f:Function, ...functions):Function {
  649. functions.unshift(f);
  650. return function(input:*):* {
  651. var result:* = null;
  652. _(functions).each(function(f:Function):void {
  653. result = f.call(this, result || input);
  654. });
  655. return result;
  656. }
  657. };
  658. /**
  659. * Returns a function that will only be executed after being called N times.
  660. */
  661. _.after = function(times:int, func:Function):Function {
  662. if (times <= 0) return func();
  663. return function():* {
  664. if (--times < 1) { return func.apply(this, arguments); }
  665. };
  666. };
  667. /* OBJECTS */
  668. /**
  669. * Returns a list of the collection's keys. Returns ints if given an array.
  670. */
  671. _.keys = function(obj:*):Array {
  672. var keys:Array = [];
  673. if (obj is Array) {
  674. return _.range(0, obj.length);
  675. } else {
  676. for (var key:String in obj) {
  677. obj.hasOwnProperty(key) && keys.push(key);
  678. }
  679. }
  680. return keys;
  681. };
  682. /** Returns a list of the collection's values. */
  683. _.values = function(obj:*):Array {
  684. return _.map(obj, identity);
  685. };
  686. /** Returns a list of the names of all functions in the collection. */
  687. _.functions = _.methods = function(obj:*):Array {
  688. return _.filter(_.keys(obj), function(key:String):Boolean {
  689. return obj[key] is Function;
  690. }).sort();
  691. };
  692. /**
  693. * Extends the object with all the properties in the supplied object(s).
  694. * Alters and returns the original object, NOT a copy. To extend a copy,
  695. * var copy:Object = _(obj).chain().clone().extend(otherObj).value().
  696. *
  697. * An object can be extended with arrays, though this would not be a
  698. * terribly sensible thing to do. The opposite doesn't work at all. Fails
  699. * on XMLLists without even throwing ArgumentError, because why would you
  700. * even try to do that, seriously?
  701. */
  702. _.extend = function(obj:Object, ...args):Object {
  703. each(args, function(source:Object):void {
  704. for (var key:String in source) {
  705. obj[key] = source[key];
  706. }
  707. });
  708. return obj;
  709. };
  710. /**
  711. * Creates a shallow copy of an Array or Object.
  712. *
  713. * @throws ArgumentError if supplied an XMLList.
  714. */
  715. _.clone = function(obj:*):* {
  716. if (obj is XMLList) throw new ArgumentError("_.clone cannot operate on an XMLList.");
  717. return (obj is Array) ? obj.slice() : _({}).extend(obj);
  718. };
  719. /**
  720. * Insert this in a method chain to invoke the interceptor on the object
  721. * being chained at that point.
  722. */
  723. _.tap = function(obj:*, interceptor:Function):* {
  724. interceptor(obj);
  725. return obj;
  726. };
  727. /*
  728. * All the _.isFoo functions from underscore.js are omitted, since AS3's
  729. * equality and "is" operators do a good enough job.
  730. */
  731. /* UTILITY */
  732. /**
  733. * Run a function n times. Optional third context argument. The function
  734. * itself may accept the loop counter as its argument.
  735. */
  736. _.times = function(n:int, f:Function, context:Object = null):void {
  737. for (var i:int = 0; i < n; i++) {
  738. safeCall(f, context, i);
  739. }
  740. };
  741. _.mixin = function(obj:*):void {
  742. each(_.functions(obj), function(name:String):void {
  743. // remember, the value of an assignment statement is the value that was assigned.
  744. addToWrapper(name, _[name] = obj[name]);
  745. });
  746. };
  747. var eq:Function = function(a:*, b:*, stack:*):Boolean {
  748. // Identical objects are equal. `0 === -0`, but they aren't identical.
  749. // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
  750. if (a === b) return a !== 0 || 1 / a == 1 / b;
  751. // A strict comparison is necessary because `null == undefined`.
  752. if (a == null || b == null) return a === b;
  753. // Unwrap any wrapped objects.
  754. if (a is Wrapper) a = (a as Wrapper)._wrapped;
  755. if (b is Wrapper) b = (b as Wrapper)._wrapped;
  756. // Invoke a custom `isEqual` method if one is provided.
  757. try {
  758. if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
  759. if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
  760. } catch (e:Error) {}
  761. // Compare `[[Class]]` names.
  762. var className:String = a.toString();
  763. if (className != b.toString()) return false;
  764. switch (className) {
  765. // Strings, numbers, dates, and booleans are compared by value.
  766. case '[object String]':
  767. // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
  768. // equivalent to `new String("5")`.
  769. return a == String(b);
  770. case '[object Number]':
  771. // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
  772. // other numeric values.
  773. return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
  774. case '[object Date]':
  775. case '[object Boolean]':
  776. // Coerce dates and booleans to numeric primitive values. Dates are compared by their
  777. // millisecond representations. Note that invalid dates with millisecond representations
  778. // of `NaN` are not equivalent.
  779. return +a == +b;
  780. // RegExps are compared by their source patterns and flags.
  781. case '[object RegExp]':
  782. return a.source == b.source &&
  783. a.global == b.global &&
  784. a.multiline == b.multiline &&
  785. a.ignoreCase == b.ignoreCase;
  786. }
  787. if (typeof a != 'object' || typeof b != 'object') return false;
  788. // Assume equality for cyclic structures. The algorithm for detecting cyclic
  789. // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
  790. var length:uint = stack.length;
  791. while (length--) {
  792. // Linear search. Performance is inversely proportional to the number of
  793. // unique nested structures.
  794. if (stack[length] == a) return true;
  795. }
  796. // Add the first object to the stack of traversed objects.
  797. stack.push(a);
  798. var size:uint = 0, result:Boolean = true;
  799. // Recursively compare objects and arrays.
  800. if (className == '[object Array]') {
  801. // Compare array lengths to determine if a deep comparison is necessary.
  802. size = a.length;
  803. result = size == b.length;
  804. if (result) {
  805. // Deep compare the contents, ignoring non-numeric properties.
  806. while (size--) {
  807. // Ensure commutative equality for sparse arrays.
  808. if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
  809. }
  810. }
  811. } else {
  812. // Objects are compared via their serialized state.
  813. var serializedA:ByteArray = new ByteArray();
  814. serializedA.writeObject(a);
  815. var serializedB:ByteArray = new ByteArray();
  816. serializedB.writeObject(b);
  817. return serializedA.toString() === serializedB.toString();
  818. }
  819. // Remove the first object from the stack of traversed objects.
  820. stack.pop();
  821. return result;
  822. };
  823. // Perform a deep comparison to check if two objects are equal.
  824. _.isEqual = function(a:*, b:*):Boolean {
  825. return eq(a, b, []);
  826. };
  827. _.isFunction = function(a:*):Boolean {
  828. return (a is Function);
  829. };
  830. // Has own property?
  831. _.has = function(obj:*, key:*):Boolean {
  832. return obj.hasOwnProperty(key);
  833. };
  834. /** A default iterator used for functions which can optionally detect truthy values. */
  835. var identity:Function = function(value:*, index:* = null, list:* = null):* {
  836. return value;
  837. };
  838. /* OOP WRAPPER */
  839. _.prototype = Wrapper.prototype;
  840. var result:Function = function(obj:*, chain:Boolean):* {
  841. // if chaining, continue the chaining wrapper; otherwise return naked object
  842. return chain ? _(obj).chain() : obj;
  843. }
  844. var addToWrapper:Function = function(name:String, fn:Function):void {
  845. Wrapper.prototype[name] = function(...args):* {
  846. return result(fn.apply(_, [this._wrapped].concat(args)), this._chain);
  847. };
  848. };
  849. _.mixin(_);
  850. return _;
  851. })();
  852. }