scope.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. var _ = require('../util')
  2. var Observer = require('../observer')
  3. var Binding = require('../binding')
  4. /**
  5. * Setup the data scope of an instance.
  6. *
  7. * We need to setup the instance $observer, which emits
  8. * data change events. The $observer relays events from
  9. * the $data's observer, because $data might be swapped
  10. * and the data observer might change.
  11. *
  12. * If the instance has a parent and is not isolated, we
  13. * also need to listen to parent scope events and propagate
  14. * changes down here.
  15. */
  16. exports._initScope = function () {
  17. this._initData()
  18. this._initComputed()
  19. this._initMethods()
  20. this._initMeta()
  21. }
  22. /**
  23. * Initialize the data.
  24. */
  25. exports._initData = function () {
  26. // proxy data on instance
  27. var data = this._data
  28. var keys = Object.keys(data)
  29. var i = keys.length
  30. while (i--) {
  31. this._proxy(keys[i])
  32. }
  33. // observe data
  34. Observer.create(data)
  35. }
  36. /**
  37. * Swap the isntance's $data. Called in $data's setter.
  38. *
  39. * @param {Object} newData
  40. */
  41. exports._setData = function (newData) {
  42. var oldData = this._data
  43. this._data = newData
  44. var keys, key, i
  45. // unproxy keys not present in new data
  46. keys = Object.keys(oldData)
  47. i = keys.length
  48. while (i--) {
  49. key = keys[i]
  50. if (!_.isReserved(key) && !(key in newData)) {
  51. this._unproxy(key)
  52. }
  53. }
  54. // proxy keys not already proxied,
  55. // and trigger change for changed values
  56. keys = Object.keys(newData)
  57. i = keys.length
  58. while (i--) {
  59. key = keys[i]
  60. if (!this.hasOwnProperty(key) && !_.isReserved(key)) {
  61. // new property
  62. this._proxy(key)
  63. }
  64. }
  65. Observer.create(newData)
  66. this._digest()
  67. }
  68. /**
  69. * Proxy a property, so that
  70. * vm.prop === vm._data.prop
  71. *
  72. * @param {String} key
  73. */
  74. exports._proxy = function (key) {
  75. if (!_.isReserved(key)) {
  76. // need to store ref to self here
  77. // because these getter/setters might
  78. // be called by child instances!
  79. var self = this
  80. Object.defineProperty(self, key, {
  81. configurable: true,
  82. enumerable: true,
  83. get: function proxyGetter () {
  84. return self._data[key]
  85. },
  86. set: function proxySetter (val) {
  87. self._data[key] = val
  88. }
  89. })
  90. }
  91. }
  92. /**
  93. * Unproxy a property.
  94. *
  95. * @param {String} key
  96. */
  97. exports._unproxy = function (key) {
  98. delete this[key]
  99. }
  100. /**
  101. * Force update on every watcher in scope.
  102. */
  103. exports._digest = function () {
  104. var i = this._watcherList.length
  105. while (i--) {
  106. this._watcherList[i].update()
  107. }
  108. var children = this._children
  109. var child
  110. if (children) {
  111. i = children.length
  112. while (i--) {
  113. child = children[i]
  114. if (!child.$options.isolated) {
  115. child._digest()
  116. }
  117. }
  118. }
  119. }
  120. /**
  121. * Setup computed properties. They are essentially
  122. * special getter/setters
  123. */
  124. function noop () {}
  125. exports._initComputed = function () {
  126. var computed = this.$options.computed
  127. if (computed) {
  128. for (var key in computed) {
  129. var def = computed[key]
  130. if (typeof def === 'function') {
  131. def = {
  132. get: _.bind(def, this),
  133. set: noop
  134. }
  135. } else {
  136. def.get = def.get
  137. ? _.bind(def.get, this)
  138. : noop
  139. def.set = def.set
  140. ? _.bind(def.set, this)
  141. : noop
  142. }
  143. def.enumerable = true
  144. def.configurable = true
  145. Object.defineProperty(this, key, def)
  146. }
  147. }
  148. }
  149. /**
  150. * Setup instance methods. Methods must be bound to the
  151. * instance since they might be called by children
  152. * inheriting them.
  153. */
  154. exports._initMethods = function () {
  155. var methods = this.$options.methods
  156. if (methods) {
  157. for (var key in methods) {
  158. this[key] = _.bind(methods[key], this)
  159. }
  160. }
  161. }
  162. /**
  163. * Initialize meta information like $index, $key & $value.
  164. */
  165. exports._initMeta = function () {
  166. var metas = this.$options._meta
  167. if (metas) {
  168. for (var key in metas) {
  169. this._defineMeta(key, metas[key])
  170. }
  171. }
  172. }
  173. /**
  174. * Define a meta property, e.g $index, $key, $value
  175. * which only exists on the vm instance but not in $data.
  176. *
  177. * @param {String} key
  178. * @param {*} value
  179. */
  180. exports._defineMeta = function (key, value) {
  181. var binding = new Binding()
  182. Object.defineProperty(this, key, {
  183. enumerable: true,
  184. configurable: true,
  185. get: function metaGetter () {
  186. if (Observer.target) {
  187. Observer.target.addDep(binding)
  188. }
  189. return value
  190. },
  191. set: function metaSetter (val) {
  192. if (val !== value) {
  193. value = val
  194. binding.notify()
  195. }
  196. }
  197. })
  198. }