lifecycle.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import { replace } from '../../util/index'
  2. import Directive from '../../directive'
  3. import { compile, compileRoot, transclude } from '../../compiler/index'
  4. export default function (Vue) {
  5. /**
  6. * Update v-ref for component.
  7. *
  8. * @param {Boolean} remove
  9. */
  10. Vue.prototype._updateRef = function (remove) {
  11. var ref = this.$options._ref
  12. if (ref) {
  13. var refs = (this._scope || this._context).$refs
  14. if (remove) {
  15. if (refs[ref] === this) {
  16. refs[ref] = null
  17. }
  18. } else {
  19. refs[ref] = this
  20. }
  21. }
  22. }
  23. /**
  24. * Transclude, compile and link element.
  25. *
  26. * If a pre-compiled linker is available, that means the
  27. * passed in element will be pre-transcluded and compiled
  28. * as well - all we need to do is to call the linker.
  29. *
  30. * Otherwise we need to call transclude/compile/link here.
  31. *
  32. * @param {Element} el
  33. * @return {Element}
  34. */
  35. Vue.prototype._compile = function (el) {
  36. var options = this.$options
  37. // transclude and init element
  38. // transclude can potentially replace original
  39. // so we need to keep reference; this step also injects
  40. // the template and caches the original attributes
  41. // on the container node and replacer node.
  42. var original = el
  43. el = transclude(el, options)
  44. this._initElement(el)
  45. // root is always compiled per-instance, because
  46. // container attrs and props can be different every time.
  47. var contextOptions = this._context && this._context.$options
  48. var rootLinker = compileRoot(el, options, contextOptions)
  49. // compile and link the rest
  50. var contentLinkFn
  51. var ctor = this.constructor
  52. // component compilation can be cached
  53. // as long as it's not using inline-template
  54. if (options._linkerCachable) {
  55. contentLinkFn = ctor.linker
  56. if (!contentLinkFn) {
  57. contentLinkFn = ctor.linker = compile(el, options)
  58. }
  59. }
  60. // link phase
  61. // make sure to link root with prop scope!
  62. var rootUnlinkFn = rootLinker(this, el, this._scope)
  63. var contentUnlinkFn = contentLinkFn
  64. ? contentLinkFn(this, el)
  65. : compile(el, options)(this, el)
  66. // register composite unlink function
  67. // to be called during instance destruction
  68. this._unlinkFn = function () {
  69. rootUnlinkFn()
  70. // passing destroying: true to avoid searching and
  71. // splicing the directives
  72. contentUnlinkFn(true)
  73. }
  74. // finally replace original
  75. if (options.replace) {
  76. replace(original, el)
  77. }
  78. this._isCompiled = true
  79. this._callHook('compiled')
  80. return el
  81. }
  82. /**
  83. * Initialize instance element. Called in the public
  84. * $mount() method.
  85. *
  86. * @param {Element} el
  87. */
  88. Vue.prototype._initElement = function (el) {
  89. if (el instanceof DocumentFragment) {
  90. this._isFragment = true
  91. this.$el = this._fragmentStart = el.firstChild
  92. this._fragmentEnd = el.lastChild
  93. // set persisted text anchors to empty
  94. if (this._fragmentStart.nodeType === 3) {
  95. this._fragmentStart.data = this._fragmentEnd.data = ''
  96. }
  97. this._fragment = el
  98. } else {
  99. this.$el = el
  100. }
  101. this.$el.__vue__ = this
  102. this._callHook('beforeCompile')
  103. }
  104. /**
  105. * Create and bind a directive to an element.
  106. *
  107. * @param {String} name - directive name
  108. * @param {Node} node - target node
  109. * @param {Object} desc - parsed directive descriptor
  110. * @param {Object} def - directive definition object
  111. * @param {Vue} [host] - transclusion host component
  112. * @param {Object} [scope] - v-for scope
  113. * @param {Fragment} [frag] - owner fragment
  114. */
  115. Vue.prototype._bindDir = function (descriptor, node, host, scope, frag) {
  116. this._directives.push(
  117. new Directive(descriptor, this, node, host, scope, frag)
  118. )
  119. }
  120. /**
  121. * Teardown an instance, unobserves the data, unbind all the
  122. * directives, turn off all the event listeners, etc.
  123. *
  124. * @param {Boolean} remove - whether to remove the DOM node.
  125. * @param {Boolean} deferCleanup - if true, defer cleanup to
  126. * be called later
  127. */
  128. Vue.prototype._destroy = function (remove, deferCleanup) {
  129. if (this._isBeingDestroyed) {
  130. if (!deferCleanup) {
  131. this._cleanup()
  132. }
  133. return
  134. }
  135. var destroyReady
  136. var pendingRemoval
  137. var self = this
  138. // Cleanup should be called either synchronously or asynchronoysly as
  139. // callback of this.$remove(), or if remove and deferCleanup are false.
  140. // In any case it should be called after all other removing, unbinding and
  141. // turning of is done
  142. var cleanupIfPossible = function () {
  143. if (destroyReady && !pendingRemoval && !deferCleanup) {
  144. self._cleanup()
  145. }
  146. }
  147. // remove DOM element
  148. if (remove && this.$el) {
  149. pendingRemoval = true
  150. this.$remove(function () {
  151. pendingRemoval = false
  152. cleanupIfPossible()
  153. })
  154. }
  155. this._callHook('beforeDestroy')
  156. this._isBeingDestroyed = true
  157. var i
  158. // remove self from parent. only necessary
  159. // if parent is not being destroyed as well.
  160. var parent = this.$parent
  161. if (parent && !parent._isBeingDestroyed) {
  162. parent.$children.$remove(this)
  163. // unregister ref (remove: true)
  164. this._updateRef(true)
  165. }
  166. // destroy all children.
  167. i = this.$children.length
  168. while (i--) {
  169. this.$children[i].$destroy()
  170. }
  171. // teardown props
  172. if (this._propsUnlinkFn) {
  173. this._propsUnlinkFn()
  174. }
  175. // teardown all directives. this also tearsdown all
  176. // directive-owned watchers.
  177. if (this._unlinkFn) {
  178. this._unlinkFn()
  179. }
  180. i = this._watchers.length
  181. while (i--) {
  182. this._watchers[i].teardown()
  183. }
  184. // remove reference to self on $el
  185. if (this.$el) {
  186. this.$el.__vue__ = null
  187. }
  188. destroyReady = true
  189. cleanupIfPossible()
  190. }
  191. /**
  192. * Clean up to ensure garbage collection.
  193. * This is called after the leave transition if there
  194. * is any.
  195. */
  196. Vue.prototype._cleanup = function () {
  197. if (this._isDestroyed) {
  198. return
  199. }
  200. // remove self from owner fragment
  201. // do it in cleanup so that we can call $destroy with
  202. // defer right when a fragment is about to be removed.
  203. if (this._frag) {
  204. this._frag.children.$remove(this)
  205. }
  206. // remove reference from data ob
  207. // frozen object may not have observer.
  208. if (this._data.__ob__) {
  209. this._data.__ob__.removeVm(this)
  210. }
  211. // Clean up references to private properties and other
  212. // instances. preserve reference to _data so that proxy
  213. // accessors still work. The only potential side effect
  214. // here is that mutating the instance after it's destroyed
  215. // may affect the state of other components that are still
  216. // observing the same object, but that seems to be a
  217. // reasonable responsibility for the user rather than
  218. // always throwing an error on them.
  219. this.$el =
  220. this.$parent =
  221. this.$root =
  222. this.$children =
  223. this._watchers =
  224. this._context =
  225. this._scope =
  226. this._directives = null
  227. // call the last hook...
  228. this._isDestroyed = true
  229. this._callHook('destroyed')
  230. // turn off all instance listeners.
  231. this.$off()
  232. }
  233. }