scope.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. var _ = require('../util')
  2. var Emitter = require('../emitter')
  3. var Observer = require('../observe/observer')
  4. var scopeEvents = ['set', 'mutate', 'add', 'delete']
  5. var allEvents = ['get', 'set', 'mutate', 'add', 'delete', 'add:self', 'delete:self']
  6. /**
  7. * Setup the data scope of an instance.
  8. *
  9. * We need to setup the instance $observer, which emits
  10. * data change events. The $observer relays events from
  11. * the $data's observer, because $data might be swapped
  12. * and the data observer might change.
  13. *
  14. * If the instance has a parent and is not isolated, we
  15. * also need to listen to parent scope events and propagate
  16. * changes down here.
  17. */
  18. exports._initScope = function () {
  19. this._children = null
  20. this._childCtors = null
  21. this._initObserver()
  22. this._initData()
  23. this._initComputed()
  24. this._initMethods()
  25. // listen to parent scope events
  26. if (this.$parent && !this.$options.isolated) {
  27. this._linkScope()
  28. }
  29. }
  30. /**
  31. * Teardown the scope.
  32. */
  33. exports._teardownScope = function () {
  34. // turn of instance observer
  35. this.$observer.off()
  36. // stop relaying data events
  37. var dataOb = this._data.__ob__
  38. var proxies = this._dataProxies
  39. var i = allEvents.length
  40. var event
  41. while (i--) {
  42. event = allEvents[i]
  43. dataOb.off(event, proxies[event])
  44. }
  45. // unset data reference
  46. this._data = null
  47. // stop propagating parent scope changes
  48. if (this._scopeListeners) {
  49. this._unlinkScope()
  50. }
  51. }
  52. /**
  53. * Setup the observer and data proxy handlers.
  54. */
  55. exports._initObserver = function () {
  56. // create observer
  57. var ob = this.$observer = new Emitter(this)
  58. // setup data proxy handlers
  59. var proxies = this._dataProxies = {}
  60. allEvents.forEach(function (event) {
  61. proxies[event] = function (a, b, c) {
  62. ob.emit(event, a, b, c)
  63. }
  64. })
  65. var self = this
  66. proxies['add:self'] = function (key) {
  67. self._proxy(key)
  68. }
  69. proxies['delete:self'] = function (key) {
  70. self._unproxy(key)
  71. }
  72. }
  73. /**
  74. * Initialize the data.
  75. */
  76. exports._initData = function () {
  77. // proxy data on instance
  78. var data = this._data
  79. var keys = Object.keys(data)
  80. var i = keys.length
  81. while (i--) {
  82. this._proxy(keys[i])
  83. }
  84. // relay data changes
  85. var ob = Observer.create(data)
  86. var proxies = this._dataProxies
  87. var event
  88. i = allEvents.length
  89. while (i--) {
  90. event = allEvents[i]
  91. ob.on(event, proxies[event])
  92. }
  93. }
  94. /**
  95. * Listen to parent scope's events
  96. */
  97. exports._linkScope = function () {
  98. var self = this
  99. var ob = this.$observer
  100. var pob = this.$parent.$observer
  101. var listeners = this._scopeListeners = {}
  102. scopeEvents.forEach(function (event) {
  103. var cb = listeners[event] = function (key, a, b) {
  104. // since these events come from upstream,
  105. // we only emit them if we don't have the same keys
  106. // shadowing them in current scope.
  107. if (!self.hasOwnProperty(key)) {
  108. ob.emit(event, key, a, b, true)
  109. }
  110. }
  111. pob.on(event, cb)
  112. })
  113. }
  114. /**
  115. * Stop listening to parent scope events
  116. */
  117. exports._unlinkScope = function () {
  118. var pob = this.$parent.$observer
  119. var listeners = this._scopeListeners
  120. var i = scopeEvents.length
  121. var event
  122. while (i--) {
  123. event = scopeEvents[i]
  124. pob.off(event, listeners[event])
  125. }
  126. this._scopeListeners = null
  127. }
  128. /**
  129. * Swap the isntance's $data. Called in $data's setter.
  130. *
  131. * @param {Object} newData
  132. */
  133. exports._setData = function (newData) {
  134. var ob = this.$observer
  135. var oldData = this._data
  136. this._data = newData
  137. var keys, key, i
  138. // unproxy keys not present in new data
  139. keys = Object.keys(oldData)
  140. i = keys.length
  141. while (i--) {
  142. key = keys[i]
  143. if (!_.isReserved(key) && !(key in newData)) {
  144. this._unproxy(key)
  145. ob.emit('delete', key)
  146. }
  147. }
  148. // proxy keys not already proxied,
  149. // and trigger change for changed values
  150. keys = Object.keys(newData)
  151. i = keys.length
  152. while (i--) {
  153. key = keys[i]
  154. if (this.hasOwnProperty(key)) {
  155. // existing property, emit set if different
  156. if (newData[key] !== oldData[key]) {
  157. ob.emit('set', key, newData[key])
  158. }
  159. } else {
  160. // new property
  161. this._proxy(key)
  162. ob.emit('add', key)
  163. }
  164. }
  165. // teardown/setup data proxies
  166. var newOb = Observer.create(newData)
  167. var oldOb = oldData.__ob__
  168. var proxies = this._dataProxies
  169. var event, proxy
  170. i = allEvents.length
  171. while (i--) {
  172. event = allEvents[i]
  173. proxy = proxies[event]
  174. newOb.on(event, proxy)
  175. oldOb.off(event, proxy)
  176. }
  177. }
  178. /**
  179. * Proxy a property, so that
  180. * vm.prop === vm._data.prop
  181. *
  182. * @param {String} key
  183. */
  184. exports._proxy = function (key) {
  185. if (!_.isReserved(key)) {
  186. var self = this
  187. Object.defineProperty(self, key, {
  188. configurable: true,
  189. enumerable: true,
  190. get: function () {
  191. return self._data[key]
  192. },
  193. set: function (val) {
  194. self._data[key] = val
  195. }
  196. })
  197. }
  198. }
  199. /**
  200. * Unproxy a property.
  201. *
  202. * @param {String} key
  203. */
  204. exports._unproxy = function (key) {
  205. delete this[key]
  206. }
  207. /**
  208. * Setup computed properties. They are essentially
  209. * special getter/setters
  210. */
  211. function noop () {}
  212. exports._initComputed = function () {
  213. var computed = this.$options.computed
  214. if (computed) {
  215. for (var key in computed) {
  216. var def = computed[key]
  217. if (typeof def === 'function') {
  218. def = {
  219. get: def,
  220. set: noop
  221. }
  222. }
  223. def.enumerable = true
  224. def.configurable = true
  225. Object.defineProperty(this, key, def)
  226. }
  227. }
  228. }
  229. /**
  230. * Setup instance methods. Methods must be bound to the
  231. * instance since they might be called by children
  232. * inheriting them.
  233. */
  234. exports._initMethods = function () {
  235. var methods = this.$options.methods
  236. if (methods) {
  237. for (var key in methods) {
  238. this[key] = _.bind(methods[key], this)
  239. }
  240. }
  241. }
  242. /**
  243. * Create a child instance that prototypally inehrits
  244. * data on parent. To achieve that we create an intermediate
  245. * constructor with its prototype pointing to parent.
  246. *
  247. * @param {Object} opts
  248. * @param {Function} [BaseCtor]
  249. */
  250. exports._addChild = function (opts, BaseCtor) {
  251. BaseCtor = BaseCtor || _.Vue
  252. var ChildVue
  253. if (BaseCtor.options.isolated) {
  254. ChildVue = BaseCtor
  255. } else {
  256. var parent = this
  257. var ctors = parent._childCtors
  258. if (!ctors) {
  259. ctors = parent._childCtors = {}
  260. }
  261. ChildVue = ctors[BaseCtor.cid]
  262. if (!ChildVue) {
  263. ChildVue = function (options) {
  264. this.$parent = parent
  265. this.$root = parent.$root || parent
  266. this.constructor = ChildVue
  267. _.Vue.call(this, options)
  268. }
  269. ChildVue.options = BaseCtor.options
  270. ChildVue.prototype = this
  271. ctors[BaseCtor.cid] = ChildVue
  272. }
  273. }
  274. var child = new ChildVue(opts)
  275. if (!this._children) {
  276. this._children = []
  277. }
  278. this._children.push(child)
  279. return child
  280. }
  281. /**
  282. * Define a meta property, e.g $index, $key, $value
  283. * which only exists on the vm instance but not in $data.
  284. *
  285. * @param {String} key
  286. * @param {*} value
  287. */
  288. exports._defineMeta = function (key, value) {
  289. if (this.hasOwnProperty('key')) {
  290. this[key] = value
  291. return
  292. }
  293. var ob = this.$observer
  294. Object.defineProperty(this, key, {
  295. enumerable: true,
  296. configurable: true,
  297. get: function () {
  298. if (Observer.emitGet) {
  299. ob.emit('get', key)
  300. }
  301. return value
  302. },
  303. set: function (val) {
  304. if (val !== value) {
  305. value = val
  306. ob.emit('set', key, val)
  307. }
  308. }
  309. })
  310. ob.emit('add', key, value)
  311. }