createArrayFromMixed.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. 'use strict';
  2. /**
  3. * Copyright (c) 2013-present, Facebook, Inc.
  4. *
  5. * This source code is licensed under the MIT license found in the
  6. * LICENSE file in the root directory of this source tree.
  7. *
  8. * @typechecks
  9. */
  10. var invariant = require('./invariant');
  11. /**
  12. * Convert array-like objects to arrays.
  13. *
  14. * This API assumes the caller knows the contents of the data type. For less
  15. * well defined inputs use createArrayFromMixed.
  16. *
  17. * @param {object|function|filelist} obj
  18. * @return {array}
  19. */
  20. function toArray(obj) {
  21. var length = obj.length;
  22. // Some browsers builtin objects can report typeof 'function' (e.g. NodeList
  23. // in old versions of Safari).
  24. !(!Array.isArray(obj) && (typeof obj === 'object' || typeof obj === 'function')) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'toArray: Array-like object expected') : invariant(false) : void 0;
  25. !(typeof length === 'number') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'toArray: Object needs a length property') : invariant(false) : void 0;
  26. !(length === 0 || length - 1 in obj) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'toArray: Object should have keys for indices') : invariant(false) : void 0;
  27. !(typeof obj.callee !== 'function') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'toArray: Object can\'t be `arguments`. Use rest params ' + '(function(...args) {}) or Array.from() instead.') : invariant(false) : void 0;
  28. // Old IE doesn't give collections access to hasOwnProperty. Assume inputs
  29. // without method will throw during the slice call and skip straight to the
  30. // fallback.
  31. if (obj.hasOwnProperty) {
  32. try {
  33. return Array.prototype.slice.call(obj);
  34. } catch (e) {
  35. // IE < 9 does not support Array#slice on collections objects
  36. }
  37. }
  38. // Fall back to copying key by key. This assumes all keys have a value,
  39. // so will not preserve sparsely populated inputs.
  40. var ret = Array(length);
  41. for (var ii = 0; ii < length; ii++) {
  42. ret[ii] = obj[ii];
  43. }
  44. return ret;
  45. }
  46. /**
  47. * Perform a heuristic test to determine if an object is "array-like".
  48. *
  49. * A monk asked Joshu, a Zen master, "Has a dog Buddha nature?"
  50. * Joshu replied: "Mu."
  51. *
  52. * This function determines if its argument has "array nature": it returns
  53. * true if the argument is an actual array, an `arguments' object, or an
  54. * HTMLCollection (e.g. node.childNodes or node.getElementsByTagName()).
  55. *
  56. * It will return false for other array-like objects like Filelist.
  57. *
  58. * @param {*} obj
  59. * @return {boolean}
  60. */
  61. function hasArrayNature(obj) {
  62. return (
  63. // not null/false
  64. !!obj && (
  65. // arrays are objects, NodeLists are functions in Safari
  66. typeof obj == 'object' || typeof obj == 'function') &&
  67. // quacks like an array
  68. 'length' in obj &&
  69. // not window
  70. !('setInterval' in obj) &&
  71. // no DOM node should be considered an array-like
  72. // a 'select' element has 'length' and 'item' properties on IE8
  73. typeof obj.nodeType != 'number' && (
  74. // a real array
  75. Array.isArray(obj) ||
  76. // arguments
  77. 'callee' in obj ||
  78. // HTMLCollection/NodeList
  79. 'item' in obj)
  80. );
  81. }
  82. /**
  83. * Ensure that the argument is an array by wrapping it in an array if it is not.
  84. * Creates a copy of the argument if it is already an array.
  85. *
  86. * This is mostly useful idiomatically:
  87. *
  88. * var createArrayFromMixed = require('createArrayFromMixed');
  89. *
  90. * function takesOneOrMoreThings(things) {
  91. * things = createArrayFromMixed(things);
  92. * ...
  93. * }
  94. *
  95. * This allows you to treat `things' as an array, but accept scalars in the API.
  96. *
  97. * If you need to convert an array-like object, like `arguments`, into an array
  98. * use toArray instead.
  99. *
  100. * @param {*} obj
  101. * @return {array}
  102. */
  103. function createArrayFromMixed(obj) {
  104. if (!hasArrayNature(obj)) {
  105. return [obj];
  106. } else if (Array.isArray(obj)) {
  107. return obj.slice();
  108. } else {
  109. return toArray(obj);
  110. }
  111. }
  112. module.exports = createArrayFromMixed;