index.js 6.2 KB

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