scope.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. var _ = require('../util')
  2. var Observer = require('../observer')
  3. var Dep = require('../observer/dep')
  4. /**
  5. * Setup the scope of an instance, which contains:
  6. * - observed data
  7. * - computed properties
  8. * - user methods
  9. * - meta properties
  10. */
  11. exports._initScope = function () {
  12. this._initProps()
  13. this._initData()
  14. this._initComputed()
  15. this._initMethods()
  16. this._initMeta()
  17. }
  18. /**
  19. * Initialize props.
  20. */
  21. exports._initProps = function () {
  22. // make sure all props properties are observed
  23. var data = this._data
  24. var props = this.$options.props
  25. var prop, key, i
  26. if (props) {
  27. i = props.length
  28. while (i--) {
  29. prop = props[i]
  30. // props can be strings or object descriptors
  31. key = _.camelize(
  32. typeof prop === 'string'
  33. ? prop
  34. : prop.name
  35. )
  36. if (!(key in data) && key !== '$data') {
  37. data[key] = undefined
  38. }
  39. }
  40. }
  41. }
  42. /**
  43. * Initialize the data.
  44. */
  45. exports._initData = function () {
  46. // proxy data on instance
  47. var data = this._data
  48. var keys = Object.keys(data)
  49. var i, key
  50. i = keys.length
  51. while (i--) {
  52. key = keys[i]
  53. if (!_.isReserved(key)) {
  54. this._proxy(key)
  55. }
  56. }
  57. // observe data
  58. Observer.create(data).addVm(this)
  59. }
  60. /**
  61. * Swap the isntance's $data. Called in $data's setter.
  62. *
  63. * @param {Object} newData
  64. */
  65. exports._setData = function (newData) {
  66. newData = newData || {}
  67. var oldData = this._data
  68. this._data = newData
  69. var keys, key, i
  70. // copy props.
  71. // this should only happen during a v-repeat of component
  72. // that also happens to have compiled props.
  73. var props = this.$options.props
  74. if (props) {
  75. i = props.length
  76. while (i--) {
  77. key = props[i]
  78. if (key !== '$data' && !newData.hasOwnProperty(key)) {
  79. newData.$set(key, oldData[key])
  80. }
  81. }
  82. }
  83. // unproxy keys not present in new data
  84. keys = Object.keys(oldData)
  85. i = keys.length
  86. while (i--) {
  87. key = keys[i]
  88. if (!_.isReserved(key) && !(key in newData)) {
  89. this._unproxy(key)
  90. }
  91. }
  92. // proxy keys not already proxied,
  93. // and trigger change for changed values
  94. keys = Object.keys(newData)
  95. i = keys.length
  96. while (i--) {
  97. key = keys[i]
  98. if (!this.hasOwnProperty(key) && !_.isReserved(key)) {
  99. // new property
  100. this._proxy(key)
  101. }
  102. }
  103. oldData.__ob__.removeVm(this)
  104. Observer.create(newData).addVm(this)
  105. this._digest()
  106. }
  107. /**
  108. * Proxy a property, so that
  109. * vm.prop === vm._data.prop
  110. *
  111. * @param {String} key
  112. */
  113. exports._proxy = function (key) {
  114. // need to store ref to self here
  115. // because these getter/setters might
  116. // be called by child instances!
  117. var self = this
  118. Object.defineProperty(self, key, {
  119. configurable: true,
  120. enumerable: true,
  121. get: function proxyGetter () {
  122. return self._data[key]
  123. },
  124. set: function proxySetter (val) {
  125. self._data[key] = val
  126. }
  127. })
  128. }
  129. /**
  130. * Unproxy a property.
  131. *
  132. * @param {String} key
  133. */
  134. exports._unproxy = function (key) {
  135. delete this[key]
  136. }
  137. /**
  138. * Force update on every watcher in scope.
  139. */
  140. exports._digest = function () {
  141. var i = this._watchers.length
  142. while (i--) {
  143. this._watchers[i].update()
  144. }
  145. var children = this._children
  146. i = children.length
  147. while (i--) {
  148. var child = children[i]
  149. if (child.$options.inherit) {
  150. child._digest()
  151. }
  152. }
  153. }
  154. /**
  155. * Setup computed properties. They are essentially
  156. * special getter/setters
  157. */
  158. function noop () {}
  159. exports._initComputed = function () {
  160. var computed = this.$options.computed
  161. if (computed) {
  162. for (var key in computed) {
  163. var userDef = computed[key]
  164. var def = {
  165. enumerable: true,
  166. configurable: true
  167. }
  168. if (typeof userDef === 'function') {
  169. def.get = _.bind(userDef, this)
  170. def.set = noop
  171. } else {
  172. def.get = userDef.get
  173. ? _.bind(userDef.get, this)
  174. : noop
  175. def.set = userDef.set
  176. ? _.bind(userDef.set, this)
  177. : noop
  178. }
  179. Object.defineProperty(this, key, def)
  180. }
  181. }
  182. }
  183. /**
  184. * Setup instance methods. Methods must be bound to the
  185. * instance since they might be called by children
  186. * inheriting them.
  187. */
  188. exports._initMethods = function () {
  189. var methods = this.$options.methods
  190. if (methods) {
  191. for (var key in methods) {
  192. this[key] = _.bind(methods[key], this)
  193. }
  194. }
  195. }
  196. /**
  197. * Initialize meta information like $index, $key & $value.
  198. */
  199. exports._initMeta = function () {
  200. var metas = this.$options._meta
  201. if (metas) {
  202. for (var key in metas) {
  203. this._defineMeta(key, metas[key])
  204. }
  205. }
  206. }
  207. /**
  208. * Define a meta property, e.g $index, $key, $value
  209. * which only exists on the vm instance but not in $data.
  210. *
  211. * @param {String} key
  212. * @param {*} value
  213. */
  214. exports._defineMeta = function (key, value) {
  215. var dep = new Dep()
  216. Object.defineProperty(this, key, {
  217. enumerable: true,
  218. configurable: true,
  219. get: function metaGetter () {
  220. dep.depend()
  221. return value
  222. },
  223. set: function metaSetter (val) {
  224. if (val !== value) {
  225. value = val
  226. dep.notify()
  227. }
  228. }
  229. })
  230. }