index.ts 7.0 KB

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