compiler.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. var Emitter = require('./emitter'),
  2. Observer = require('./observer'),
  3. config = require('./config'),
  4. utils = require('./utils'),
  5. Binding = require('./binding'),
  6. Directive = require('./directive'),
  7. TextParser = require('./text-parser'),
  8. DepsParser = require('./deps-parser'),
  9. ExpParser = require('./exp-parser'),
  10. // cache methods
  11. slice = [].slice,
  12. log = utils.log,
  13. makeHash = utils.hash,
  14. extend = utils.extend,
  15. def = utils.defProtected,
  16. hasOwn = ({}).hasOwnProperty,
  17. // hooks to register
  18. hooks = [
  19. 'created', 'ready',
  20. 'beforeDestroy', 'afterDestroy',
  21. 'attached', 'detached'
  22. ]
  23. /**
  24. * The DOM compiler
  25. * scans a DOM node and compile bindings for a ViewModel
  26. */
  27. function Compiler (vm, options) {
  28. var compiler = this
  29. // indicate that we are intiating this instance
  30. // so we should not run any transitions
  31. compiler.init = true
  32. // process and extend options
  33. options = compiler.options = options || makeHash()
  34. utils.processOptions(options)
  35. // copy data, methods & compiler options
  36. var data = compiler.data = options.data || {}
  37. extend(vm, data, true)
  38. extend(vm, options.methods, true)
  39. extend(compiler, options.compilerOptions)
  40. // initialize element
  41. var el = compiler.el = compiler.setupElement(options)
  42. log('\nnew VM instance:', el.tagName, '\n')
  43. // set compiler properties
  44. compiler.vm = el.vue_vm = vm
  45. compiler.bindings = makeHash()
  46. compiler.dirs = []
  47. compiler.deferred = []
  48. compiler.exps = []
  49. compiler.computed = []
  50. compiler.childCompilers = []
  51. compiler.emitter = new Emitter()
  52. compiler.emitter._ctx = vm
  53. compiler.delegators = makeHash()
  54. // set inenumerable VM properties
  55. def(vm, '$', makeHash())
  56. def(vm, '$el', el)
  57. def(vm, '$compiler', compiler)
  58. def(vm, '$root', getRoot(compiler).vm)
  59. // set parent VM
  60. // and register child id on parent
  61. var parent = compiler.parentCompiler,
  62. childId = utils.attr(el, 'ref')
  63. if (parent) {
  64. parent.childCompilers.push(compiler)
  65. def(vm, '$parent', parent.vm)
  66. if (childId) {
  67. compiler.childId = childId
  68. parent.vm.$[childId] = vm
  69. }
  70. }
  71. // setup observer
  72. compiler.setupObserver()
  73. // create bindings for computed properties
  74. var computed = options.computed
  75. if (computed) {
  76. for (var key in computed) {
  77. compiler.createBinding(key)
  78. }
  79. }
  80. // copy paramAttributes
  81. if (options.paramAttributes) {
  82. options.paramAttributes.forEach(function (attr) {
  83. var val = el.getAttribute(attr)
  84. vm[attr] = (isNaN(val) || val === null)
  85. ? val
  86. : Number(val)
  87. })
  88. }
  89. // beforeCompile hook
  90. compiler.execHook('created')
  91. // the user might have set some props on the vm
  92. // so copy it back to the data...
  93. extend(data, vm)
  94. // observe the data
  95. compiler.observeData(data)
  96. // for repeated items, create index/key bindings
  97. // because they are ienumerable
  98. if (compiler.repeat) {
  99. compiler.createBinding('$index')
  100. if (data.$key) compiler.createBinding('$key')
  101. }
  102. // now parse the DOM, during which we will create necessary bindings
  103. // and bind the parsed directives
  104. compiler.compile(el, true)
  105. // bind deferred directives (child components)
  106. compiler.deferred.forEach(compiler.bindDirective, compiler)
  107. // extract dependencies for computed properties
  108. compiler.parseDeps()
  109. // done!
  110. compiler.rawContent = null
  111. compiler.init = false
  112. // post compile / ready hook
  113. if (!compiler.delayReady) {
  114. compiler.execHook('ready')
  115. }
  116. }
  117. var CompilerProto = Compiler.prototype
  118. /**
  119. * Initialize the VM/Compiler's element.
  120. * Fill it in with the template if necessary.
  121. */
  122. CompilerProto.setupElement = function (options) {
  123. // create the node first
  124. var el = typeof options.el === 'string'
  125. ? document.querySelector(options.el)
  126. : options.el || document.createElement(options.tagName || 'div')
  127. var template = options.template
  128. if (template) {
  129. // collect anything already in there
  130. /* jshint boss: true */
  131. var child,
  132. frag = this.rawContent = document.createDocumentFragment()
  133. while (child = el.firstChild) {
  134. frag.appendChild(child)
  135. }
  136. // replace option: use the first node in
  137. // the template directly
  138. if (options.replace && template.childNodes.length === 1) {
  139. var replacer = template.childNodes[0].cloneNode(true)
  140. if (el.parentNode) {
  141. el.parentNode.insertBefore(replacer, el)
  142. el.parentNode.removeChild(el)
  143. }
  144. el = replacer
  145. } else {
  146. el.appendChild(template.cloneNode(true))
  147. }
  148. }
  149. // apply element options
  150. if (options.id) el.id = options.id
  151. if (options.className) el.className = options.className
  152. var attrs = options.attributes
  153. if (attrs) {
  154. for (var attr in attrs) {
  155. el.setAttribute(attr, attrs[attr])
  156. }
  157. }
  158. return el
  159. }
  160. /**
  161. * Setup observer.
  162. * The observer listens for get/set/mutate events on all VM
  163. * values/objects and trigger corresponding binding updates.
  164. * It also listens for lifecycle hooks.
  165. */
  166. CompilerProto.setupObserver = function () {
  167. var compiler = this,
  168. bindings = compiler.bindings,
  169. options = compiler.options,
  170. observer = compiler.observer = new Emitter()
  171. // a hash to hold event proxies for each root level key
  172. // so they can be referenced and removed later
  173. observer.proxies = makeHash()
  174. observer._ctx = compiler.vm
  175. // add own listeners which trigger binding updates
  176. observer
  177. .on('get', onGet)
  178. .on('set', onSet)
  179. .on('mutate', onSet)
  180. // register hooks
  181. hooks.forEach(function (hook) {
  182. var fns = options[hook]
  183. if (Array.isArray(fns)) {
  184. var i = fns.length
  185. // since hooks were merged with child at head,
  186. // we loop reversely.
  187. while (i--) {
  188. registerHook(hook, fns[i])
  189. }
  190. } else if (fns) {
  191. registerHook(hook, fns)
  192. }
  193. })
  194. // broadcast attached/detached hooks
  195. observer
  196. .on('hook:attached', function () {
  197. broadcast(1)
  198. })
  199. .on('hook:detached', function () {
  200. broadcast(0)
  201. })
  202. function onGet (key) {
  203. check(key)
  204. DepsParser.catcher.emit('get', bindings[key])
  205. }
  206. function onSet (key, val, mutation) {
  207. observer.emit('change:' + key, val, mutation)
  208. check(key)
  209. bindings[key].update(val)
  210. }
  211. function registerHook (hook, fn) {
  212. observer.on('hook:' + hook, function () {
  213. fn.call(compiler.vm, options)
  214. })
  215. }
  216. function broadcast (event) {
  217. var children = compiler.childCompilers
  218. if (children) {
  219. var child, i = children.length
  220. while (i--) {
  221. child = children[i]
  222. if (child.el.parentNode) {
  223. event = 'hook:' + (event ? 'attached' : 'detached')
  224. child.observer.emit(event)
  225. child.emitter.emit(event)
  226. }
  227. }
  228. }
  229. }
  230. function check (key) {
  231. if (!bindings[key]) {
  232. compiler.createBinding(key)
  233. }
  234. }
  235. }
  236. CompilerProto.observeData = function (data) {
  237. var compiler = this,
  238. observer = compiler.observer
  239. // recursively observe nested properties
  240. Observer.observe(data, '', observer)
  241. // also create binding for top level $data
  242. // so it can be used in templates too
  243. var $dataBinding = compiler.bindings['$data'] = new Binding(compiler, '$data')
  244. $dataBinding.update(data)
  245. // allow $data to be swapped
  246. defGetSet(compiler.vm, '$data', {
  247. enumerable: false,
  248. get: function () {
  249. compiler.observer.emit('get', '$data')
  250. return compiler.data
  251. },
  252. set: function (newData) {
  253. var oldData = compiler.data
  254. Observer.unobserve(oldData, '', observer)
  255. compiler.data = newData
  256. Observer.copyPaths(newData, oldData)
  257. Observer.observe(newData, '', observer)
  258. compiler.observer.emit('set', '$data', newData)
  259. }
  260. })
  261. // emit $data change on all changes
  262. observer
  263. .on('set', onSet)
  264. .on('mutate', onSet)
  265. function onSet (key) {
  266. if (key !== '$data') {
  267. $dataBinding.update(compiler.data)
  268. }
  269. }
  270. }
  271. /**
  272. * Compile a DOM node (recursive)
  273. */
  274. CompilerProto.compile = function (node, root) {
  275. var compiler = this,
  276. nodeType = node.nodeType,
  277. tagName = node.tagName
  278. if (nodeType === 1 && tagName !== 'SCRIPT') { // a normal node
  279. // skip anything with v-pre
  280. if (utils.attr(node, 'pre') !== null) return
  281. // special attributes to check
  282. var repeatExp,
  283. withExp,
  284. partialId,
  285. directive,
  286. componentId = utils.attr(node, 'component') || tagName.toLowerCase(),
  287. componentCtor = compiler.getOption('components', componentId)
  288. // It is important that we access these attributes
  289. // procedurally because the order matters.
  290. //
  291. // `utils.attr` removes the attribute once it gets the
  292. // value, so we should not access them all at once.
  293. // v-repeat has the highest priority
  294. // and we need to preserve all other attributes for it.
  295. /* jshint boss: true */
  296. if (repeatExp = utils.attr(node, 'repeat')) {
  297. // repeat block cannot have v-id at the same time.
  298. directive = Directive.parse('repeat', repeatExp, compiler, node)
  299. if (directive) {
  300. directive.Ctor = componentCtor
  301. // defer child component compilation
  302. // so by the time they are compiled, the parent
  303. // would have collected all bindings
  304. compiler.deferred.push(directive)
  305. }
  306. // v-with has 2nd highest priority
  307. } else if (root !== true && ((withExp = utils.attr(node, 'with')) || componentCtor)) {
  308. withExp = Directive.split(withExp || '')
  309. withExp.forEach(function (exp, i) {
  310. var directive = Directive.parse('with', exp, compiler, node)
  311. if (directive) {
  312. directive.Ctor = componentCtor
  313. // notify the directive that this is the
  314. // last expression in the group
  315. directive.last = i === withExp.length - 1
  316. compiler.deferred.push(directive)
  317. }
  318. })
  319. } else {
  320. // check transition & animation properties
  321. node.vue_trans = utils.attr(node, 'transition')
  322. node.vue_anim = utils.attr(node, 'animation')
  323. node.vue_effect = utils.attr(node, 'effect')
  324. // replace innerHTML with partial
  325. partialId = utils.attr(node, 'partial')
  326. if (partialId) {
  327. var partial = compiler.getOption('partials', partialId)
  328. if (partial) {
  329. node.innerHTML = ''
  330. node.appendChild(partial.cloneNode(true))
  331. }
  332. }
  333. // finally, only normal directives left!
  334. compiler.compileNode(node)
  335. }
  336. } else if (nodeType === 3) { // text node
  337. compiler.compileTextNode(node)
  338. }
  339. }
  340. /**
  341. * Compile a normal node
  342. */
  343. CompilerProto.compileNode = function (node) {
  344. var i, j,
  345. attrs = slice.call(node.attributes),
  346. prefix = config.prefix + '-'
  347. // parse if has attributes
  348. if (attrs && attrs.length) {
  349. var attr, isDirective, exps, exp, directive, dirname
  350. // loop through all attributes
  351. i = attrs.length
  352. while (i--) {
  353. attr = attrs[i]
  354. isDirective = false
  355. if (attr.name.indexOf(prefix) === 0) {
  356. // a directive - split, parse and bind it.
  357. isDirective = true
  358. exps = Directive.split(attr.value)
  359. // loop through clauses (separated by ",")
  360. // inside each attribute
  361. j = exps.length
  362. while (j--) {
  363. exp = exps[j]
  364. dirname = attr.name.slice(prefix.length)
  365. directive = Directive.parse(dirname, exp, this, node)
  366. if (directive) {
  367. this.bindDirective(directive)
  368. }
  369. }
  370. } else {
  371. // non directive attribute, check interpolation tags
  372. exp = TextParser.parseAttr(attr.value)
  373. if (exp) {
  374. directive = Directive.parse('attr', attr.name + ':' + exp, this, node)
  375. if (directive) {
  376. this.bindDirective(directive)
  377. }
  378. }
  379. }
  380. if (isDirective && dirname !== 'cloak') {
  381. node.removeAttribute(attr.name)
  382. }
  383. }
  384. }
  385. // recursively compile childNodes
  386. if (node.childNodes.length) {
  387. slice.call(node.childNodes).forEach(this.compile, this)
  388. }
  389. }
  390. /**
  391. * Compile a text node
  392. */
  393. CompilerProto.compileTextNode = function (node) {
  394. var tokens = TextParser.parse(node.nodeValue)
  395. if (!tokens) return
  396. var el, token, directive, partial, partialId, partialNodes
  397. for (var i = 0, l = tokens.length; i < l; i++) {
  398. token = tokens[i]
  399. directive = partialNodes = null
  400. if (token.key) { // a binding
  401. if (token.key.charAt(0) === '>') { // a partial
  402. partialId = token.key.slice(1).trim()
  403. if (partialId === 'yield') {
  404. el = this.rawContent
  405. } else {
  406. partial = this.getOption('partials', partialId)
  407. if (partial) {
  408. el = partial.cloneNode(true)
  409. } else {
  410. utils.warn('Unknown partial: ' + partialId)
  411. continue
  412. }
  413. }
  414. if (el) {
  415. // save an Array reference of the partial's nodes
  416. // so we can compile them AFTER appending the fragment
  417. partialNodes = slice.call(el.childNodes)
  418. }
  419. } else { // a real binding
  420. if (!token.html) { // text binding
  421. el = document.createTextNode('')
  422. directive = Directive.parse('text', token.key, this, el)
  423. } else { // html binding
  424. el = document.createComment(config.prefix + '-html')
  425. directive = Directive.parse('html', token.key, this, el)
  426. }
  427. }
  428. } else { // a plain string
  429. el = document.createTextNode(token)
  430. }
  431. // insert node
  432. node.parentNode.insertBefore(el, node)
  433. // bind directive
  434. if (directive) {
  435. this.bindDirective(directive)
  436. }
  437. // compile partial after appending, because its children's parentNode
  438. // will change from the fragment to the correct parentNode.
  439. // This could affect directives that need access to its element's parentNode.
  440. if (partialNodes) {
  441. partialNodes.forEach(this.compile, this)
  442. }
  443. }
  444. node.parentNode.removeChild(node)
  445. }
  446. /**
  447. * Add a directive instance to the correct binding & viewmodel
  448. */
  449. CompilerProto.bindDirective = function (directive) {
  450. // keep track of it so we can unbind() later
  451. this.dirs.push(directive)
  452. // for empty or literal directives, simply call its bind()
  453. // and we're done.
  454. if (directive.isEmpty || directive.isLiteral) {
  455. if (directive.bind) directive.bind()
  456. return
  457. }
  458. // otherwise, we got more work to do...
  459. var binding,
  460. compiler = this,
  461. key = directive.key
  462. if (directive.isExp) {
  463. // expression bindings are always created on current compiler
  464. binding = compiler.createBinding(key, true, directive.isFn)
  465. } else {
  466. // recursively locate which compiler owns the binding
  467. while (compiler) {
  468. if (compiler.hasKey(key)) {
  469. break
  470. } else {
  471. compiler = compiler.parentCompiler
  472. }
  473. }
  474. compiler = compiler || this
  475. binding = compiler.bindings[key] || compiler.createBinding(key)
  476. }
  477. binding.dirs.push(directive)
  478. directive.binding = binding
  479. var value = binding.val()
  480. // invoke bind hook if exists
  481. if (directive.bind) {
  482. directive.bind(value)
  483. }
  484. // set initial value
  485. directive.update(value, true)
  486. }
  487. /**
  488. * Create binding and attach getter/setter for a key to the viewmodel object
  489. */
  490. CompilerProto.createBinding = function (key, isExp, isFn) {
  491. log(' created binding: ' + key)
  492. var compiler = this,
  493. bindings = compiler.bindings,
  494. computed = compiler.options.computed,
  495. binding = new Binding(compiler, key, isExp, isFn)
  496. if (isExp) {
  497. // expression bindings are anonymous
  498. compiler.defineExp(key, binding)
  499. } else {
  500. bindings[key] = binding
  501. if (binding.root) {
  502. // this is a root level binding. we need to define getter/setters for it.
  503. if (computed && computed[key]) {
  504. // computed property
  505. compiler.defineComputed(key, binding, computed[key])
  506. } else if (key.charAt(0) !== '$') {
  507. // normal property
  508. compiler.defineProp(key, binding)
  509. } else {
  510. compiler.defineMeta(key, binding)
  511. }
  512. } else {
  513. // ensure path in data so it can be observed
  514. Observer.ensurePath(compiler.data, key)
  515. var parentKey = key.slice(0, key.lastIndexOf('.'))
  516. if (!bindings[parentKey]) {
  517. // this is a nested value binding, but the binding for its parent
  518. // has not been created yet. We better create that one too.
  519. compiler.createBinding(parentKey)
  520. }
  521. }
  522. }
  523. return binding
  524. }
  525. /**
  526. * Define the getter/setter for a root-level property on the VM
  527. * and observe the initial value
  528. */
  529. CompilerProto.defineProp = function (key, binding) {
  530. var compiler = this,
  531. data = compiler.data,
  532. ob = data.__emitter__
  533. // make sure the key is present in data
  534. // so it can be observed
  535. if (!(key in data)) {
  536. data[key] = undefined
  537. }
  538. // if the data object is already observed, but the key
  539. // is not observed, we need to add it to the observed keys.
  540. if (ob && !(key in ob.values)) {
  541. Observer.convert(data, key)
  542. }
  543. binding.value = data[key]
  544. defGetSet(compiler.vm, key, {
  545. get: function () {
  546. return compiler.data[key]
  547. },
  548. set: function (val) {
  549. compiler.data[key] = val
  550. }
  551. })
  552. }
  553. /**
  554. * Define a meta property, e.g. $index or $key,
  555. * which is bindable but only accessible on the VM,
  556. * not in the data.
  557. */
  558. CompilerProto.defineMeta = function (key, binding) {
  559. var vm = this.vm,
  560. ob = this.observer,
  561. value = binding.value = vm[key] || this.data[key]
  562. // remove initital meta in data, since the same piece
  563. // of data can be observed by different VMs, each have
  564. // its own associated meta info.
  565. delete this.data[key]
  566. defGetSet(vm, key, {
  567. get: function () {
  568. if (Observer.shouldGet) ob.emit('get', key)
  569. return value
  570. },
  571. set: function (val) {
  572. ob.emit('set', key, val)
  573. value = val
  574. }
  575. })
  576. }
  577. /**
  578. * Define an expression binding, which is essentially
  579. * an anonymous computed property
  580. */
  581. CompilerProto.defineExp = function (key, binding) {
  582. var getter = ExpParser.parse(key, this)
  583. if (getter) {
  584. this.markComputed(binding, getter)
  585. this.exps.push(binding)
  586. }
  587. }
  588. /**
  589. * Define a computed property on the VM
  590. */
  591. CompilerProto.defineComputed = function (key, binding, value) {
  592. this.markComputed(binding, value)
  593. defGetSet(this.vm, key, {
  594. get: binding.value.$get,
  595. set: binding.value.$set
  596. })
  597. }
  598. /**
  599. * Process a computed property binding
  600. * so its getter/setter are bound to proper context
  601. */
  602. CompilerProto.markComputed = function (binding, value) {
  603. binding.isComputed = true
  604. // bind the accessors to the vm
  605. if (binding.isFn) {
  606. binding.value = value
  607. } else {
  608. if (typeof value === 'function') {
  609. value = { $get: value }
  610. }
  611. binding.value = {
  612. $get: utils.bind(value.$get, this.vm),
  613. $set: value.$set
  614. ? utils.bind(value.$set, this.vm)
  615. : undefined
  616. }
  617. }
  618. // keep track for dep parsing later
  619. this.computed.push(binding)
  620. }
  621. /**
  622. * Retrive an option from the compiler
  623. */
  624. CompilerProto.getOption = function (type, id) {
  625. var opts = this.options,
  626. parent = this.parentCompiler,
  627. globalAssets = config.globalAssets
  628. return (opts[type] && opts[type][id]) || (
  629. parent
  630. ? parent.getOption(type, id)
  631. : globalAssets[type] && globalAssets[type][id]
  632. )
  633. }
  634. /**
  635. * Emit lifecycle events to trigger hooks
  636. */
  637. CompilerProto.execHook = function (event) {
  638. event = 'hook:' + event
  639. this.observer.emit(event)
  640. this.emitter.emit(event)
  641. }
  642. /**
  643. * Check if a compiler's data contains a keypath
  644. */
  645. CompilerProto.hasKey = function (key) {
  646. var baseKey = key.split('.')[0]
  647. return hasOwn.call(this.data, baseKey) ||
  648. hasOwn.call(this.vm, baseKey)
  649. }
  650. /**
  651. * Collect dependencies for computed properties
  652. */
  653. CompilerProto.parseDeps = function () {
  654. if (!this.computed.length) return
  655. DepsParser.parse(this.computed)
  656. }
  657. /**
  658. * Add an event delegation listener
  659. * listeners are instances of directives with `isFn:true`
  660. */
  661. CompilerProto.addListener = function (listener) {
  662. var event = listener.arg,
  663. delegator = this.delegators[event]
  664. if (!delegator) {
  665. // initialize a delegator
  666. delegator = this.delegators[event] = {
  667. targets: [],
  668. handler: function (e) {
  669. var i = delegator.targets.length,
  670. target
  671. while (i--) {
  672. target = delegator.targets[i]
  673. if (target.el.contains(e.target) && target.handler) {
  674. target.handler(e)
  675. }
  676. }
  677. }
  678. }
  679. this.el.addEventListener(event, delegator.handler)
  680. }
  681. delegator.targets.push(listener)
  682. }
  683. /**
  684. * Remove an event delegation listener
  685. */
  686. CompilerProto.removeListener = function (listener) {
  687. var targets = this.delegators[listener.arg].targets
  688. targets.splice(targets.indexOf(listener), 1)
  689. }
  690. /**
  691. * Unbind and remove element
  692. */
  693. CompilerProto.destroy = function () {
  694. // avoid being called more than once
  695. // this is irreversible!
  696. if (this.destroyed) return
  697. var compiler = this,
  698. i, key, dir, dirs, binding,
  699. vm = compiler.vm,
  700. el = compiler.el,
  701. directives = compiler.dirs,
  702. exps = compiler.exps,
  703. bindings = compiler.bindings,
  704. delegators = compiler.delegators
  705. compiler.execHook('beforeDestroy')
  706. // unobserve data
  707. Observer.unobserve(compiler.data, '', compiler.observer)
  708. // unbind all direcitves
  709. i = directives.length
  710. while (i--) {
  711. dir = directives[i]
  712. // if this directive is an instance of an external binding
  713. // e.g. a directive that refers to a variable on the parent VM
  714. // we need to remove it from that binding's directives
  715. // * empty and literal bindings do not have binding.
  716. if (dir.binding && dir.binding.compiler !== compiler) {
  717. dirs = dir.binding.dirs
  718. if (dirs) dirs.splice(dirs.indexOf(dir), 1)
  719. }
  720. dir.unbind()
  721. }
  722. // unbind all expressions (anonymous bindings)
  723. i = exps.length
  724. while (i--) {
  725. exps[i].unbind()
  726. }
  727. // unbind all own bindings
  728. for (key in bindings) {
  729. binding = bindings[key]
  730. if (binding) {
  731. binding.unbind()
  732. }
  733. }
  734. // remove all event delegators
  735. for (key in delegators) {
  736. el.removeEventListener(key, delegators[key].handler)
  737. }
  738. // remove self from parentCompiler
  739. var parent = compiler.parentCompiler,
  740. childId = compiler.childId
  741. if (parent) {
  742. parent.childCompilers.splice(parent.childCompilers.indexOf(compiler), 1)
  743. if (childId) {
  744. delete parent.vm.$[childId]
  745. }
  746. }
  747. // finally remove dom element
  748. if (el === document.body) {
  749. el.innerHTML = ''
  750. } else {
  751. vm.$remove()
  752. }
  753. el.vue_vm = null
  754. this.destroyed = true
  755. // emit destroy hook
  756. compiler.execHook('afterDestroy')
  757. // finally, unregister all listeners
  758. compiler.observer.off()
  759. compiler.emitter.off()
  760. }
  761. // Helpers --------------------------------------------------------------------
  762. /**
  763. * shorthand for getting root compiler
  764. */
  765. function getRoot (compiler) {
  766. while (compiler.parentCompiler) {
  767. compiler = compiler.parentCompiler
  768. }
  769. return compiler
  770. }
  771. /**
  772. * for convenience & minification
  773. */
  774. function defGetSet (obj, key, def) {
  775. Object.defineProperty(obj, key, def)
  776. }
  777. module.exports = Compiler