scope.js 5.3 KB

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