state.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import Watcher from '../../observer/watcher'
  2. import Dep from '../../observer/dep'
  3. import { observe } from '../../observer/index'
  4. import {
  5. warn,
  6. hasOwn,
  7. isReserved,
  8. isPlainObject,
  9. bind
  10. } from '../../util/index'
  11. export default function (Vue) {
  12. /**
  13. * Accessor for `$data` property, since setting $data
  14. * requires observing the new object and updating
  15. * proxied properties.
  16. */
  17. Object.defineProperty(Vue.prototype, '$data', {
  18. get () {
  19. return this._data
  20. },
  21. set (newData) {
  22. if (newData !== this._data) {
  23. this._setData(newData)
  24. }
  25. }
  26. })
  27. /**
  28. * Setup the scope of an instance, which contains:
  29. * - observed data
  30. * - computed properties
  31. * - user methods
  32. * - meta properties
  33. */
  34. Vue.prototype._initState = function () {
  35. this._initMethods()
  36. this._initData()
  37. this._initComputed()
  38. }
  39. /**
  40. * Initialize the data.
  41. */
  42. Vue.prototype._initData = function () {
  43. var data = this.$options.data
  44. data = this._data = typeof data === 'function'
  45. ? data()
  46. : data || {}
  47. if (!isPlainObject(data)) {
  48. data = {}
  49. process.env.NODE_ENV !== 'production' && warn(
  50. 'data functions should return an object.',
  51. this
  52. )
  53. }
  54. // proxy data on instance
  55. var keys = Object.keys(data)
  56. var i = keys.length
  57. while (i--) {
  58. this._proxy(keys[i])
  59. }
  60. // observe data
  61. observe(data, this)
  62. }
  63. /**
  64. * Swap the instance's $data. Called in $data's setter.
  65. *
  66. * @param {Object} newData
  67. */
  68. Vue.prototype._setData = function (newData) {
  69. newData = newData || {}
  70. var oldData = this._data
  71. this._data = newData
  72. var keys, key, i
  73. // unproxy keys not present in new data
  74. keys = Object.keys(oldData)
  75. i = keys.length
  76. while (i--) {
  77. key = keys[i]
  78. if (!(key in newData)) {
  79. this._unproxy(key)
  80. }
  81. }
  82. // proxy keys not already proxied,
  83. // and trigger change for changed values
  84. keys = Object.keys(newData)
  85. i = keys.length
  86. while (i--) {
  87. key = keys[i]
  88. if (!hasOwn(this, key)) {
  89. // new property
  90. this._proxy(key)
  91. }
  92. }
  93. oldData.__ob__.removeVm(this)
  94. observe(newData, this)
  95. this._digest()
  96. }
  97. /**
  98. * Proxy a property, so that
  99. * vm.prop === vm._data.prop
  100. *
  101. * @param {String} key
  102. */
  103. Vue.prototype._proxy = function (key) {
  104. if (!isReserved(key)) {
  105. // need to store ref to self here
  106. // because these getter/setters might
  107. // be called by child scopes via
  108. // prototype inheritance.
  109. var self = this
  110. Object.defineProperty(self, key, {
  111. configurable: true,
  112. enumerable: true,
  113. get: function proxyGetter () {
  114. return self._data[key]
  115. },
  116. set: function proxySetter (val) {
  117. self._data[key] = val
  118. }
  119. })
  120. }
  121. }
  122. /**
  123. * Unproxy a property.
  124. *
  125. * @param {String} key
  126. */
  127. Vue.prototype._unproxy = function (key) {
  128. if (!isReserved(key)) {
  129. delete this[key]
  130. }
  131. }
  132. /**
  133. * Setup computed properties. They are essentially
  134. * special getter/setters
  135. */
  136. function noop () {}
  137. Vue.prototype._initComputed = function () {
  138. var computed = this.$options.computed
  139. if (computed) {
  140. for (var key in computed) {
  141. var userDef = computed[key]
  142. var def = {
  143. enumerable: true,
  144. configurable: true
  145. }
  146. if (typeof userDef === 'function') {
  147. def.get = makeComputedGetter(userDef, this)
  148. def.set = noop
  149. } else {
  150. def.get = userDef.get
  151. ? userDef.cache !== false
  152. ? makeComputedGetter(userDef.get, this)
  153. : bind(userDef.get, this)
  154. : noop
  155. def.set = userDef.set
  156. ? bind(userDef.set, this)
  157. : noop
  158. }
  159. Object.defineProperty(this, key, def)
  160. }
  161. }
  162. }
  163. function makeComputedGetter (getter, owner) {
  164. var watcher = new Watcher(owner, getter, null, {
  165. lazy: true
  166. })
  167. return function computedGetter () {
  168. if (watcher.dirty) {
  169. watcher.evaluate()
  170. }
  171. if (Dep.target) {
  172. watcher.depend()
  173. }
  174. return watcher.value
  175. }
  176. }
  177. /**
  178. * Setup instance methods. Methods must be bound to the
  179. * instance since they might be passed down as a prop to
  180. * child components.
  181. */
  182. Vue.prototype._initMethods = function () {
  183. var methods = this.$options.methods
  184. if (methods) {
  185. for (var key in methods) {
  186. this[key] = bind(methods[key], this)
  187. }
  188. }
  189. }
  190. }