index.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. /* @flow */
  2. import config from '../config'
  3. import Dep from './dep'
  4. import { arrayMethods } from './array'
  5. import {
  6. def,
  7. isObject,
  8. isPlainObject,
  9. hasProto,
  10. hasOwn,
  11. warn
  12. } from '../util/index'
  13. const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
  14. /**
  15. * By default, when a reactive property is set, the new value is
  16. * also converted to become reactive. However when passing down props,
  17. * we don't want to force conversion because the value may be a nested value
  18. * under a frozen data structure. Converting it would defeat the optimization.
  19. */
  20. export const observerState = {
  21. shouldConvert: true,
  22. isSettingProps: false
  23. }
  24. /**
  25. * Observer class that are attached to each observed
  26. * object. Once attached, the observer converts target
  27. * object's property keys into getter/setters that
  28. * collect dependencies and dispatches updates.
  29. */
  30. export class Observer {
  31. value: any;
  32. dep: Dep;
  33. vmCount: number; // number of vms that has this object as root $data
  34. constructor (value: any) {
  35. this.value = value
  36. this.dep = new Dep()
  37. this.vmCount = 0
  38. def(value, '__ob__', this)
  39. if (Array.isArray(value)) {
  40. const augment = hasProto
  41. ? protoAugment
  42. : copyAugment
  43. augment(value, arrayMethods, arrayKeys)
  44. this.observeArray(value)
  45. } else {
  46. this.walk(value)
  47. }
  48. }
  49. /**
  50. * Walk through each property and convert them into
  51. * getter/setters. This method should only be called when
  52. * value type is Object.
  53. */
  54. walk (obj: Object) {
  55. const val = this.value
  56. for (const key in obj) {
  57. defineReactive(val, key, obj[key])
  58. }
  59. }
  60. /**
  61. * Observe a list of Array items.
  62. */
  63. observeArray (items: Array<any>) {
  64. for (let i = 0, l = items.length; i < l; i++) {
  65. observe(items[i])
  66. }
  67. }
  68. }
  69. // helpers
  70. /**
  71. * Augment an target Object or Array by intercepting
  72. * the prototype chain using __proto__
  73. */
  74. function protoAugment (target, src: Object) {
  75. /* eslint-disable no-proto */
  76. target.__proto__ = src
  77. /* eslint-enable no-proto */
  78. }
  79. /**
  80. * Augment an target Object or Array by defining
  81. * hidden properties.
  82. *
  83. * istanbul ignore next
  84. */
  85. function copyAugment (target: Object, src: Object, keys: Array<string>) {
  86. for (let i = 0, l = keys.length; i < l; i++) {
  87. const key = keys[i]
  88. def(target, key, src[key])
  89. }
  90. }
  91. /**
  92. * Attempt to create an observer instance for a value,
  93. * returns the new observer if successfully observed,
  94. * or the existing observer if the value already has one.
  95. */
  96. export function observe (value: any): Observer | void {
  97. if (!isObject(value)) {
  98. return
  99. }
  100. let ob: Observer | void
  101. if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  102. ob = value.__ob__
  103. } else if (
  104. observerState.shouldConvert &&
  105. !config._isServer &&
  106. (Array.isArray(value) || isPlainObject(value)) &&
  107. Object.isExtensible(value) &&
  108. !value._isVue
  109. ) {
  110. ob = new Observer(value)
  111. }
  112. return ob
  113. }
  114. /**
  115. * Define a reactive property on an Object.
  116. */
  117. export function defineReactive (
  118. obj: Object,
  119. key: string,
  120. val: any,
  121. customSetter?: Function
  122. ) {
  123. const dep = new Dep()
  124. const property = Object.getOwnPropertyDescriptor(obj, key)
  125. if (property && property.configurable === false) {
  126. return
  127. }
  128. // cater for pre-defined getter/setters
  129. const getter = property && property.get
  130. const setter = property && property.set
  131. let childOb = observe(val)
  132. Object.defineProperty(obj, key, {
  133. enumerable: true,
  134. configurable: true,
  135. get: function reactiveGetter () {
  136. const value = getter ? getter.call(obj) : val
  137. if (Dep.target) {
  138. dep.depend()
  139. if (childOb) {
  140. childOb.dep.depend()
  141. }
  142. if (Array.isArray(value)) {
  143. for (let e, i = 0, l = value.length; i < l; i++) {
  144. e = value[i]
  145. e && e.__ob__ && e.__ob__.dep.depend()
  146. }
  147. }
  148. }
  149. return value
  150. },
  151. set: function reactiveSetter (newVal) {
  152. const value = getter ? getter.call(obj) : val
  153. if (newVal === value) {
  154. return
  155. }
  156. if (process.env.NODE_ENV !== 'production' && customSetter) {
  157. customSetter()
  158. }
  159. if (setter) {
  160. setter.call(obj, newVal)
  161. } else {
  162. val = newVal
  163. }
  164. childOb = observe(newVal)
  165. dep.notify()
  166. }
  167. })
  168. }
  169. /**
  170. * Set a property on an object. Adds the new property and
  171. * triggers change notification if the property doesn't
  172. * already exist.
  173. */
  174. export function set (obj: Array<any> | Object, key: any, val: any) {
  175. if (Array.isArray(obj)) {
  176. obj.splice(key, 1, val)
  177. return val
  178. }
  179. if (hasOwn(obj, key)) {
  180. obj[key] = val
  181. return
  182. }
  183. const ob = obj.__ob__
  184. if (obj._isVue || (ob && ob.vmCount)) {
  185. process.env.NODE_ENV !== 'production' && warn(
  186. 'Avoid adding reactive properties to a Vue instance or its root $data ' +
  187. 'at runtime - declare it upfront in the data option.'
  188. )
  189. return
  190. }
  191. if (!ob) {
  192. obj[key] = val
  193. return
  194. }
  195. defineReactive(ob.value, key, val)
  196. ob.dep.notify()
  197. return val
  198. }
  199. /**
  200. * Delete a property and trigger change if necessary.
  201. */
  202. export function del (obj: Object, key: string) {
  203. const ob = obj.__ob__
  204. if (obj._isVue || (ob && ob.vmCount)) {
  205. process.env.NODE_ENV !== 'production' && warn(
  206. 'Avoid deleting properties on a Vue instance or its root $data ' +
  207. '- just set it to null.'
  208. )
  209. return
  210. }
  211. if (!hasOwn(obj, key)) {
  212. return
  213. }
  214. delete obj[key]
  215. if (!ob) {
  216. return
  217. }
  218. ob.dep.notify()
  219. }