util.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. export function isObject(value) {
  2. const type = typeof value
  3. return value != null && (type === 'object' || type === 'function')
  4. }
  5. export function eq(value, other) {
  6. return value === other || (value !== value && other !== other)
  7. }
  8. function baseAssignValue(object, key, value) {
  9. if (key === '__proto__') {
  10. Object.defineProperty(object, key, {
  11. 'configurable': true,
  12. 'enumerable': true,
  13. 'value': value,
  14. 'writable': true,
  15. })
  16. } else {
  17. object[key] = value
  18. }
  19. }
  20. /** Used to check objects for own properties. */
  21. const hasOwnProperty = Object.prototype.hasOwnProperty
  22. function assignValue(object, key, value) {
  23. const objValue = object[key]
  24. if (!(hasOwnProperty.call(object, key) && eq(objValue, value))) {
  25. if (value !== 0 || (1 / value) === (1 / objValue)) {
  26. baseAssignValue(object, key, value)
  27. }
  28. } else if (value === undefined && !(key in object)) {
  29. baseAssignValue(object, key, value)
  30. }
  31. }
  32. /** Used as references for various `Number` constants. */
  33. const INFINITY = 1 / 0
  34. function toKey(value) {
  35. if (typeof value === 'string') {
  36. return value
  37. }
  38. const result = `${value}`
  39. return (result === '0' && (1 / value) === -INFINITY) ? '-0' : result
  40. }
  41. /** Used to match property names within property paths. */
  42. const reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/
  43. const reIsPlainProp = /^\w*$/
  44. function isKey(value, object) {
  45. if (Array.isArray(value)) {
  46. return false
  47. }
  48. const type = typeof value
  49. if (type === 'number' || type === 'boolean' || value == null) {
  50. return true
  51. }
  52. return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
  53. (object != null && value in Object(object))
  54. }
  55. const charCodeOfDot = '.'.charCodeAt(0)
  56. const reEscapeChar = /\\(\\)?/g
  57. const rePropName = RegExp(
  58. // Match anything that isn't a dot or bracket.
  59. '[^.[\\]]+' + '|' +
  60. // Or match property names within brackets.
  61. '\\[(?:' +
  62. // Match a non-string expression.
  63. '([^"\'][^[]*)' + '|' +
  64. // Or match strings (supports escaping characters).
  65. '(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' +
  66. ')\\]'+ '|' +
  67. // Or match "" as the space between consecutive dots or empty brackets.
  68. '(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))'
  69. , 'g')
  70. function stringToPath(string) {
  71. const result = []
  72. if (string.charCodeAt(0) === charCodeOfDot) {
  73. result.push('')
  74. }
  75. string.replace(rePropName, (match, expression, quote, subString) => {
  76. let key = match
  77. if (quote) {
  78. key = subString.replace(reEscapeChar, '$1')
  79. }
  80. else if (expression) {
  81. key = expression.trim()
  82. }
  83. result.push(key)
  84. })
  85. return result
  86. }
  87. function castPath(value, object) {
  88. if (Array.isArray(value)) {
  89. return value
  90. }
  91. return isKey(value, object) ? [value] : stringToPath(value)
  92. }
  93. /** Used as references for various `Number` constants. */
  94. const MAX_SAFE_INTEGER = 9007199254740991
  95. /** Used to detect unsigned integer values. */
  96. const reIsUint = /^(?:0|[1-9]\d*)$/
  97. function isIndex(value, length) {
  98. const type = typeof value
  99. length = length == null ? MAX_SAFE_INTEGER : length
  100. return !!length &&
  101. (type === 'number' ||
  102. (type !== 'symbol' && reIsUint.test(value))) &&
  103. (value > -1 && value % 1 === 0 && value < length)
  104. }
  105. function baseSet(object, path, value, customizer) {
  106. if (!isObject(object)) {
  107. return object
  108. }
  109. path = castPath(path, object)
  110. const length = path.length
  111. const lastIndex = length - 1
  112. let index = -1
  113. let nested = object
  114. while (nested != null && ++index < length) {
  115. const key = toKey(path[index])
  116. let newValue = value
  117. if (index !== lastIndex) {
  118. const objValue = nested[key]
  119. newValue = customizer ? customizer(objValue, key, nested) : undefined
  120. if (newValue === undefined) {
  121. newValue = isObject(objValue)
  122. ? objValue
  123. : (isIndex(path[index + 1]) ? [] : {})
  124. }
  125. }
  126. assignValue(nested, key, newValue)
  127. nested = nested[key]
  128. }
  129. return object
  130. }
  131. export function set(object, path, value) {
  132. return object == null ? object : baseSet(object, path, value)
  133. }
  134. function baseGet(object, path) {
  135. path = castPath(path, object)
  136. let index = 0
  137. const length = path.length
  138. while (object != null && index < length) {
  139. object = object[toKey(path[index++])]
  140. }
  141. return (index && index === length) ? object : undefined
  142. }
  143. export function get(object, path, defaultValue) {
  144. const result = object == null ? undefined : baseGet(object, path)
  145. return result === undefined ? defaultValue : result
  146. }