8e1bac56cf42226394a77013de9de7118169f5884a4157684e27a6225562cb922ef1fb37cd57949912857a35a7acd95ef3bde3cfe3b4a8a7d02bbcecbf65c8 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. /* @flow */
  2. import Dep from './dep'
  3. import VNode from '../vdom/vnode'
  4. import { arrayMethods } from './array'
  5. import {
  6. def,
  7. warn,
  8. hasOwn,
  9. hasProto,
  10. isObject,
  11. isPlainObject,
  12. isPrimitive,
  13. isUndef,
  14. isValidArrayIndex,
  15. isServerRendering
  16. } from '../util/index'
  17. const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
  18. /**
  19. * In some cases we may want to disable observation inside a component's
  20. * update computation.
  21. */
  22. export let shouldObserve: boolean = true
  23. export function toggleObserving (value: boolean) {
  24. shouldObserve = value
  25. }
  26. /**
  27. * Observer class that is attached to each observed
  28. * object. Once attached, the observer converts the target
  29. * object's property keys into getter/setters that
  30. * collect dependencies and dispatch updates.
  31. */
  32. export class Observer {
  33. value: any;
  34. dep: Dep;
  35. vmCount: number; // number of vms that have this object as root $data
  36. constructor (value: any) {
  37. this.value = value
  38. this.dep = new Dep()
  39. this.vmCount = 0
  40. def(value, '__ob__', this)
  41. if (Array.isArray(value)) {
  42. if (hasProto) {
  43. protoAugment(value, arrayMethods)
  44. } else {
  45. copyAugment(value, arrayMethods, arrayKeys)
  46. }
  47. this.observeArray(value)
  48. } else {
  49. this.walk(value)
  50. }
  51. }
  52. /**
  53. * Walk through all properties and convert them into
  54. * getter/setters. This method should only be called when
  55. * value type is Object.
  56. */
  57. walk (obj: Object) {
  58. const keys = Object.keys(obj)
  59. for (let i = 0; i < keys.length; i++) {
  60. defineReactive(obj, keys[i])
  61. }
  62. }
  63. /**
  64. * Observe a list of Array items.
  65. */
  66. observeArray (items: Array<any>) {
  67. for (let i = 0, l = items.length; i < l; i++) {
  68. observe(items[i])
  69. }
  70. }
  71. }
  72. // helpers
  73. /**
  74. * Augment a target Object or Array by intercepting
  75. * the prototype chain using __proto__
  76. */
  77. function protoAugment (target, src: Object) {
  78. /* eslint-disable no-proto */
  79. target.__proto__ = src
  80. /* eslint-enable no-proto */
  81. }
  82. /**
  83. * Augment a target Object or Array by defining
  84. * hidden properties.
  85. */
  86. /* istanbul ignore next */
  87. function copyAugment (target: Object, src: Object, keys: Array<string>) {
  88. for (let i = 0, l = keys.length; i < l; i++) {
  89. const key = keys[i]
  90. def(target, key, src[key])
  91. }
  92. }
  93. /**
  94. * Attempt to create an observer instance for a value,
  95. * returns the new observer if successfully observed,
  96. * or the existing observer if the value already has one.
  97. */
  98. export function observe (value: any, asRootData: ?boolean): Observer | void {
  99. if (!isObject(value) || value instanceof VNode) {
  100. return
  101. }
  102. let ob: Observer | void
  103. if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  104. ob = value.__ob__
  105. } else if (
  106. shouldObserve &&
  107. !isServerRendering() &&
  108. (Array.isArray(value) || isPlainObject(value)) &&
  109. Object.isExtensible(value) &&
  110. !value._isVue
  111. ) {
  112. ob = new Observer(value)
  113. }
  114. if (asRootData && ob) {
  115. ob.vmCount++
  116. }
  117. return ob
  118. }
  119. /**
  120. * Define a reactive property on an Object.
  121. */
  122. export function defineReactive (
  123. obj: Object,
  124. key: string,
  125. val: any,
  126. customSetter?: ?Function,
  127. shallow?: boolean
  128. ) {
  129. const dep = new Dep()
  130. const property = Object.getOwnPropertyDescriptor(obj, key)
  131. if (property && property.configurable === false) {
  132. return
  133. }
  134. // cater for pre-defined getter/setters
  135. const getter = property && property.get
  136. const setter = property && property.set
  137. if ((!getter || setter) && arguments.length === 2) {
  138. val = obj[key]
  139. }
  140. let childOb = !shallow && observe(val)
  141. Object.defineProperty(obj, key, {
  142. enumerable: true,
  143. configurable: true,
  144. get: function reactiveGetter () {
  145. const value = getter ? getter.call(obj) : val
  146. if (Dep.target) {
  147. dep.depend()
  148. if (childOb) {
  149. childOb.dep.depend()
  150. if (Array.isArray(value)) {
  151. dependArray(value)
  152. }
  153. }
  154. }
  155. return value
  156. },
  157. set: function reactiveSetter (newVal) {
  158. const value = getter ? getter.call(obj) : val
  159. /* eslint-disable no-self-compare */
  160. if (newVal === value || (newVal !== newVal && value !== value)) {
  161. return
  162. }
  163. /* eslint-enable no-self-compare */
  164. if (process.env.NODE_ENV !== 'production' && customSetter) {
  165. customSetter()
  166. }
  167. // #7981: for accessor properties without setter
  168. if (getter && !setter) return
  169. if (setter) {
  170. setter.call(obj, newVal)
  171. } else {
  172. val = newVal
  173. }
  174. childOb = !shallow && observe(newVal)
  175. dep.notify()
  176. }
  177. })
  178. }
  179. /**
  180. * Set a property on an object. Adds the new property and
  181. * triggers change notification if the property doesn't
  182. * already exist.
  183. */
  184. export function set (target: Array<any> | Object, key: any, val: any): any {
  185. if (process.env.NODE_ENV !== 'production' &&
  186. (isUndef(target) || isPrimitive(target))
  187. ) {
  188. warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  189. }
  190. if (Array.isArray(target) && isValidArrayIndex(key)) {
  191. target.length = Math.max(target.length, key)
  192. target.splice(key, 1, val)
  193. return val
  194. }
  195. if (key in target && !(key in Object.prototype)) {
  196. target[key] = val
  197. return val
  198. }
  199. const ob = (target: any).__ob__
  200. if (target._isVue || (ob && ob.vmCount)) {
  201. process.env.NODE_ENV !== 'production' && warn(
  202. 'Avoid adding reactive properties to a Vue instance or its root $data ' +
  203. 'at runtime - declare it upfront in the data option.'
  204. )
  205. return val
  206. }
  207. if (!ob) {
  208. target[key] = val
  209. return val
  210. }
  211. defineReactive(ob.value, key, val)
  212. ob.dep.notify()
  213. return val
  214. }
  215. /**
  216. * Delete a property and trigger change if necessary.
  217. */
  218. export function del (target: Array<any> | Object, key: any) {
  219. if (process.env.NODE_ENV !== 'production' &&
  220. (isUndef(target) || isPrimitive(target))
  221. ) {
  222. warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  223. }
  224. if (Array.isArray(target) && isValidArrayIndex(key)) {
  225. target.splice(key, 1)
  226. return
  227. }
  228. const ob = (target: any).__ob__
  229. if (target._isVue || (ob && ob.vmCount)) {
  230. process.env.NODE_ENV !== 'production' && warn(
  231. 'Avoid deleting properties on a Vue instance or its root $data ' +
  232. '- just set it to null.'
  233. )
  234. return
  235. }
  236. if (!hasOwn(target, key)) {
  237. return
  238. }
  239. delete target[key]
  240. if (!ob) {
  241. return
  242. }
  243. ob.dep.notify()
  244. }
  245. /**
  246. * Collect dependencies on array elements when the array is touched, since
  247. * we cannot intercept array element access like property getters.
  248. */
  249. function dependArray (value: Array<any>) {
  250. for (let e, i = 0, l = value.length; i < l; i++) {
  251. e = value[i]
  252. e && e.__ob__ && e.__ob__.dep.depend()
  253. if (Array.isArray(e)) {
  254. dependArray(e)
  255. }
  256. }
  257. }