| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- var _ = require('./util')
- var config = require('./config')
- var Observer = require('./observer')
- var expParser = require('./parsers/expression')
- var batcher = require('./batcher')
- var uid = 0
- /**
- * A watcher parses an expression, collects dependencies,
- * and fires callback when the expression value changes.
- * This is used for both the $watch() api and directives.
- *
- * @param {Vue} vm
- * @param {String} expression
- * @param {Function} cb
- * @param {Object} options
- * - {Array} filters
- * - {Boolean} twoWay
- * - {Boolean} deep
- * - {Boolean} user
- * @constructor
- */
- function Watcher (vm, expression, cb, options) {
- this.vm = vm
- vm._watcherList.push(this)
- this.expression = expression
- this.cbs = [cb]
- this.id = ++uid // uid for batching
- this.active = true
- options = options || {}
- this.deep = options.deep
- this.user = options.user
- this.deps = Object.create(null)
- // setup filters if any.
- // We delegate directive filters here to the watcher
- // because they need to be included in the dependency
- // collection process.
- if (options.filters) {
- this.readFilters = options.filters.read
- this.writeFilters = options.filters.write
- }
- // parse expression for getter/setter
- var res = expParser.parse(expression, options.twoWay)
- this.getter = res.get
- this.setter = res.set
- this.value = this.get()
- }
- var p = Watcher.prototype
- /**
- * Add a dependency to this directive.
- *
- * @param {Dep} dep
- */
- p.addDep = function (dep) {
- var id = dep.id
- if (!this.newDeps[id]) {
- this.newDeps[id] = dep
- if (!this.deps[id]) {
- this.deps[id] = dep
- dep.addSub(this)
- }
- }
- }
- /**
- * Evaluate the getter, and re-collect dependencies.
- */
- p.get = function () {
- this.beforeGet()
- var vm = this.vm
- var value
- try {
- value = this.getter.call(vm, vm)
- } catch (e) {
- _.warn(
- 'Error when evaluating expression "' +
- this.expression + '":\n ' + e
- )
- }
- // "touch" every property so they are all tracked as
- // dependencies for deep watching
- if (this.deep) {
- traverse(value)
- }
- value = _.applyFilters(value, this.readFilters, vm)
- this.afterGet()
- return value
- }
- /**
- * Set the corresponding value with the setter.
- *
- * @param {*} value
- */
- p.set = function (value) {
- var vm = this.vm
- value = _.applyFilters(
- value, this.writeFilters, vm, this.value
- )
- try {
- this.setter.call(vm, vm, value)
- } catch (e) {
- _.warn(
- 'Error when evaluating setter "' +
- this.expression + '":\n ' + e
- )
- }
- }
- /**
- * Prepare for dependency collection.
- */
- p.beforeGet = function () {
- Observer.target = this
- this.newDeps = {}
- }
- /**
- * Clean up for dependency collection.
- */
- p.afterGet = function () {
- Observer.target = null
- for (var id in this.deps) {
- if (!this.newDeps[id]) {
- this.deps[id].removeSub(this)
- }
- }
- this.deps = this.newDeps
- }
- /**
- * Subscriber interface.
- * Will be called when a dependency changes.
- */
- p.update = function () {
- if (!config.async || config.debug) {
- this.run()
- } else {
- batcher.push(this)
- }
- }
- /**
- * Batcher job interface.
- * Will be called by the batcher.
- */
- p.run = function () {
- if (this.active) {
- var value = this.get()
- if (
- (typeof value === 'object' && value !== null) ||
- value !== this.value
- ) {
- var oldValue = this.value
- this.value = value
- var cbs = this.cbs
- for (var i = 0, l = cbs.length; i < l; i++) {
- cbs[i](value, oldValue)
- // if a callback also removed other callbacks,
- // we need to adjust the loop accordingly.
- var removed = l - cbs.length
- if (removed) {
- i -= removed
- l -= removed
- }
- }
- }
- }
- }
- /**
- * Add a callback.
- *
- * @param {Function} cb
- */
- p.addCb = function (cb) {
- this.cbs.push(cb)
- }
- /**
- * Remove a callback.
- *
- * @param {Function} cb
- */
- p.removeCb = function (cb) {
- var cbs = this.cbs
- if (cbs.length > 1) {
- var i = cbs.indexOf(cb)
- if (i > -1) {
- cbs.splice(i, 1)
- }
- } else if (cb === cbs[0]) {
- this.teardown()
- }
- }
- /**
- * Remove self from all dependencies' subcriber list.
- */
- p.teardown = function () {
- if (this.active) {
- // remove self from vm's watcher list
- // we can skip this if the vm if being destroyed
- // which can improve teardown performance.
- if (!this.vm._isBeingDestroyed) {
- var list = this.vm._watcherList
- list.splice(list.indexOf(this))
- }
- for (var id in this.deps) {
- this.deps[id].removeSub(this)
- }
- this.active = false
- this.vm = this.cbs = this.value = null
- }
- }
- /**
- * Recrusively traverse an object to evoke all converted
- * getters, so that every nested property inside the object
- * is collected as a "deep" dependency.
- *
- * @param {Object} obj
- */
- function traverse (obj) {
- var key, val, i
- for (key in obj) {
- val = obj[key]
- if (_.isArray(val)) {
- i = val.length
- while (i--) traverse(val[i])
- } else if (_.isObject(val)) {
- traverse(val)
- }
- }
- }
- module.exports = Watcher
|