observer.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. var _ = require('../util')
  2. var Emitter = require('../emitter')
  3. var arrayAugmentations = require('./array-augmentations')
  4. var objectAugmentations = require('./object-augmentations')
  5. var uid = 0
  6. /**
  7. * Type enums
  8. */
  9. var ARRAY = 0
  10. var OBJECT = 1
  11. /**
  12. * Observer class that are attached to each observed
  13. * object. Observers can connect to each other like nodes
  14. * to map the hierarchy of data objects. Once connected,
  15. * detected change events can propagate up the nested chain.
  16. *
  17. * The constructor can be invoked without arguments to
  18. * create a value-less observer that simply listens to
  19. * other observers.
  20. *
  21. * @constructor
  22. * @extends Emitter
  23. * @param {Array|Object} value
  24. * @param {Number} type
  25. * @param {Object} [options]
  26. * - doNotAlterProto
  27. * - callbackContext
  28. */
  29. function Observer (value, type, options) {
  30. Emitter.call(this, options && options.callbackContext)
  31. this.id = ++uid
  32. this.value = value
  33. this.type = type
  34. this.parents = null
  35. this.parentsHash = null
  36. if (value) {
  37. _.define(value, '$observer', this)
  38. if (type === ARRAY) {
  39. _.augment(value, arrayAugmentations)
  40. this.link(value)
  41. } else if (type === OBJECT) {
  42. if (options && options.doNotAlterProto) {
  43. _.define(value, '$add', objectAugmentations.$add)
  44. _.define(value, '$delete', objectAugmentations.$delete)
  45. } else {
  46. _.augment(value, objectAugmentations)
  47. }
  48. this.walk(value)
  49. }
  50. }
  51. }
  52. var p = Observer.prototype = Object.create(Emitter.prototype)
  53. /**
  54. * Simply concatenating the path segments with `.` cannot
  55. * deal with keys that happen to contain the dot.
  56. *
  57. * Instead of the dot, we use the backspace character
  58. * which is much less likely to appear in property keys.
  59. */
  60. Observer.pathDelimiter = '\b'
  61. /**
  62. * Switch to globally control whether to emit get events.
  63. * Only enabled during dependency collections.
  64. */
  65. Observer.emitGet = false
  66. /**
  67. * Attempt to create an observer instance for a value,
  68. * returns the new observer if successfully observed,
  69. * or the existing observer if the value already has one.
  70. *
  71. * @param {*} value
  72. * @param {Object} [options] - see the Observer constructor.
  73. * @return {Observer|undefined}
  74. * @static
  75. */
  76. Observer.create = function (value, options) {
  77. if (value &&
  78. value.hasOwnProperty('$observer') &&
  79. value.$observer instanceof Observer) {
  80. return value.$observer
  81. } else if (_.isArray(value)) {
  82. return new Observer(value, ARRAY, options)
  83. } else if (
  84. _.isObject(value) &&
  85. !value.$scope // avoid Vue instance
  86. ) {
  87. return new Observer(value, OBJECT, options)
  88. }
  89. }
  90. /**
  91. * Walk through each property, converting them and adding
  92. * them as child. This method should only be called when
  93. * value type is Object. Properties prefixed with `$` or `_`
  94. * and accessor properties are ignored.
  95. *
  96. * @param {Object} obj
  97. */
  98. p.walk = function (obj) {
  99. var keys = Object.keys(obj)
  100. var i = keys.length
  101. var key, val, prefix
  102. while (i--) {
  103. key = keys[i]
  104. prefix = key.charCodeAt(0)
  105. if (prefix !== 0x24 && prefix !== 0x5F) { // skip $ or _
  106. val = obj[key]
  107. this.observe(key, val)
  108. this.convert(key, val)
  109. }
  110. }
  111. }
  112. /**
  113. * Link a list of Array items to the observer.
  114. *
  115. * @param {Array} items
  116. */
  117. p.link = function (items, index) {
  118. index = index || 0
  119. var i = items.length
  120. while (i--) {
  121. this.observe(i + index, items[i])
  122. }
  123. }
  124. /**
  125. * Unlink a list of Array items from the observer.
  126. *
  127. * @param {Array} items
  128. */
  129. p.unlink = function (items) {
  130. var i = items.length
  131. while (i--) {
  132. this.unobserve(items[i])
  133. }
  134. }
  135. /**
  136. * If a property is observable,
  137. * create an Observer for it, and register self as
  138. * one of its parents with the associated property key.
  139. *
  140. * @param {String} key
  141. * @param {*} val
  142. */
  143. p.observe = function (key, val) {
  144. var ob = Observer.create(val)
  145. if (ob) {
  146. // register self as a parent of the child observer.
  147. var parents = ob.parents
  148. var hash = ob.parentsHash
  149. if (!parents) {
  150. ob.parents = parents = []
  151. ob.parentsHash = hash = {}
  152. }
  153. if (hash[this.id]) {
  154. _.warn('Observing duplicate key: ' + key)
  155. return
  156. }
  157. var p = {
  158. ob: this,
  159. key: key
  160. }
  161. parents.push(p)
  162. hash[this.id] = p
  163. }
  164. }
  165. /**
  166. * Unobserve a property, removing self from
  167. * its observer's parent list.
  168. *
  169. * @param {*} val
  170. */
  171. p.unobserve = function (val) {
  172. if (val && val.$observer) {
  173. val.$observer.parentsHash[this.id] = null
  174. var parents = val.$observer.parents
  175. var i = parents.length
  176. while (i--) {
  177. if (parents[i].ob === this) {
  178. parents.splice(i, 1)
  179. break
  180. }
  181. }
  182. }
  183. }
  184. /**
  185. * Convert a property into getter/setter so we can emit
  186. * the events when the property is accessed/changed.
  187. *
  188. * @param {String} key
  189. * @param {*} val
  190. */
  191. p.convert = function (key, val) {
  192. var ob = this
  193. Object.defineProperty(ob.value, key, {
  194. enumerable: true,
  195. configurable: true,
  196. get: function () {
  197. if (Observer.emitGet) {
  198. ob.propagate('get', key)
  199. }
  200. return val
  201. },
  202. set: function (newVal) {
  203. if (newVal === val) return
  204. ob.unobserve(val)
  205. val = newVal
  206. ob.observe(key, newVal)
  207. ob.emit('set:self', key, newVal)
  208. ob.propagate('set', key, newVal)
  209. }
  210. })
  211. }
  212. /**
  213. * Emit event on self and recursively propagate all parents.
  214. *
  215. * @param {String} event
  216. * @param {String} path
  217. * @param {*} val
  218. * @param {Object|undefined} mutation
  219. */
  220. p.propagate = function (event, path, val, mutation) {
  221. this.emit(event, path, val, mutation)
  222. var parents = this.parents
  223. if (!parents) {
  224. return
  225. }
  226. var parent, key, parentPath
  227. var i = parents.length
  228. while (i--) {
  229. parent = parents[i]
  230. key = parent.key
  231. parentPath = path
  232. ? key + Observer.pathDelimiter + path
  233. : key
  234. parent.ob.propagate(event, parentPath, val, mutation)
  235. }
  236. }
  237. /**
  238. * Update child elements' parent key,
  239. * should only be called when value type is Array.
  240. */
  241. p.updateIndices = function () {
  242. var arr = this.value
  243. var i = arr.length
  244. var ob
  245. while (i--) {
  246. ob = arr[i] && arr[i].$observer
  247. if (ob) {
  248. ob.parentsHash[this.id].key = i
  249. }
  250. }
  251. }
  252. module.exports = Observer