scope.js 5.4 KB

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