diff --git a/component.json b/component.json index 4a67fd599..bfa133ef0 100644 --- a/component.json +++ b/component.json @@ -45,7 +45,6 @@ "src/directives/text.js", "src/directives/transition.js", "src/directives/with.js", - "src/emitter.js", "src/filters/array-filters.js", "src/filters/index.js", "src/instance/events.js", diff --git a/src/api/events.js b/src/api/events.js index 99222e76d..e4db1c8ee 100644 --- a/src/api/events.js +++ b/src/api/events.js @@ -1,18 +1,102 @@ +var _ = require('../util') + /** - * Proxy basic event methods on the internal emitter. + * Listen on the given `event` with `fn`. + * + * @param {String} event + * @param {Function} fn */ -;['emit', 'on', 'off', 'once'].forEach(function (method) { - var realMethod = method === 'emit' - ? 'applyEmit' - : method - exports['$' + method] = function () { - this._emitter[realMethod].apply( - this._emitter, - arguments - ) +exports.$on = function (event, fn) { + (this._events[event] || (this._events[event] = [])) + .push(fn) + // increment all parent event count by 1. + // pay a small cost here to optimize for $broadcast. + var parent = this.$parent + while (parent) { + parent._eventsCount[event] = + (parent._eventsCount[event] || 0) + 1 + parent = parent.$parent } -}) +} + +/** + * Adds an `event` listener that will be invoked a single + * time then automatically removed. + * + * @param {String} event + * @param {Function} fn + */ + +exports.$once = function (event, fn) { + var self = this + function on () { + self.$off(event, on) + fn.apply(this, arguments) + } + on.fn = fn + this.$on(event, on) +} + +/** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + */ + +exports.$off = function (event, fn) { + // all + if (!arguments.length) { + this._events = {} + return + } + // specific event + var cbs = this._events[event] + if (!cbs) return + if (arguments.length === 1) { + this._events[event] = null + return + } + // specific handler + var cb + var i = cbs.length + while (i--) { + cb = cbs[i] + if (cb === fn || cb.fn === fn) { + cbs.splice(i, 1) + break + } + } +} + +/** + * Trigger an event on self. + * + * @param {String} event + */ + +exports.$emit = function (event) { + this._cancelled = false + var cbs = this._events[event] + if (cbs) { + // avoid leaking arguments: + // http://jsperf.com/closure-with-arguments + var i = arguments.length - 1 + var args = new Array(i) + while (i--) { + args[i] = arguments[i + 1] + } + cbs = _.toArray(cbs) + i = 0 + for (var l = cbs.length; i < l; i++) { + if (cbs[i].apply(this, args) === false) { + this._eventCancelled = true + } + } + } +} /** * Recursively broadcast an event to all children instances. @@ -21,13 +105,15 @@ * @param {...*} additional arguments */ -exports.$broadcast = function () { +exports.$broadcast = function (event) { + // if no child has registered for this event, + // then there's no need to broadcast. + if (!this._eventsCount[event]) return var children = this._children - for (var i = 0, l = children.length; i < l; i++) { - var child = children[i] - var emitter = child._emitter - emitter.applyEmit.apply(emitter, arguments) - if (!emitter._cancelled) { + if (children) { + for (var i = 0, l = children.length; i < l; i++) { + var child = children[i] + child.$emit.apply(child, arguments) child.$broadcast.apply(child, arguments) } } @@ -41,12 +127,9 @@ exports.$broadcast = function () { */ exports.$dispatch = function () { - var emitter = this._emitter - emitter.applyEmit.apply(emitter, arguments) - if (!emitter._cancelled) { - var parent = this.$parent - if (parent) { - parent.$dispatch.apply(parent, arguments) - } + var parent = this.$parent + while (parent) { + parent.$emit.apply(parent, arguments) + parent = parent.$parent } } \ No newline at end of file diff --git a/src/api/lifecycle.js b/src/api/lifecycle.js index 21083bdff..017f56f05 100644 --- a/src/api/lifecycle.js +++ b/src/api/lifecycle.js @@ -35,7 +35,7 @@ exports.$mount = function (el) { this._callHook('attached') ready.call(this) } else { - this._emitter.once('hook:attached', ready) + this.$once('hook:attached', ready) } } @@ -133,6 +133,5 @@ exports.$destroy = function (remove) { this._isDestroyed = true this._callHook('afterDestroy') // turn off all instance listeners. - this._emitter.off() - this._emitter = null + this.$off() } \ No newline at end of file diff --git a/src/directives/cloak.js b/src/directives/cloak.js index ae29f3346..88a1df613 100644 --- a/src/directives/cloak.js +++ b/src/directives/cloak.js @@ -4,7 +4,7 @@ module.exports = { bind: function () { var el = this.el - this.vm._emitter.once('hook:compiled', function () { + this.vm.$once('hook:compiled', function () { el.removeAttribute(config.prefix + 'cloak') }) } diff --git a/src/emitter.js b/src/emitter.js deleted file mode 100644 index 7d505178c..000000000 --- a/src/emitter.js +++ /dev/null @@ -1,143 +0,0 @@ -var _ = require('./util') - -/** - * Simple event emitter based on component/emitter. - * - * @constructor - * @param {Object} ctx - the context to call listners with. - */ - -function Emitter (ctx) { - this._cancelled = false - this._ctx = ctx || null -} - -var p = Emitter.prototype - -/** - * Listen on the given `event` with `fn`. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - */ - -p.on = function (event, fn) { - this._cbs = this._cbs || {} - ;(this._cbs[event] || (this._cbs[event] = [])) - .push(fn) - return this -} - -/** - * Adds an `event` listener that will be invoked a single - * time then automatically removed. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - */ - -p.once = function (event, fn) { - var self = this - this._cbs = this._cbs || {} - function on () { - self.off(event, on) - fn.apply(this, arguments) - } - on.fn = fn - this.on(event, on) - return this -} - -/** - * Remove the given callback for `event` or all - * registered callbacks. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - */ - -p.off = function (event, fn) { - this._cbs = this._cbs || {} - // all - if (!arguments.length) { - this._cbs = {} - return this - } - // specific event - var callbacks = this._cbs[event] - if (!callbacks) return this - // remove all handlers - if (arguments.length === 1) { - this._cbs[event] = null - return this - } - // remove specific handler - var cb - var i = callbacks.length - while (i--) { - cb = callbacks[i] - if (cb === fn || cb.fn === fn) { - callbacks.splice(i, 1) - break - } - } - return this -} - -/** - * The internal, faster emit with fixed amount of arguments - * using Function.call. This emit assumes that callbacks - * triggered will not modify the callback list being - * iterated through. - * - * @param {Object} event - * @return {Emitter} - */ - -p.emit = function (event, a, b, c, d) { - this._cbs = this._cbs || {} - var callbacks = this._cbs[event] - if (callbacks) { - var ctx = this._ctx - for (var i = 0, l = callbacks.length; i < l; i++) { - callbacks[i].call(ctx, a, b, c, d) - } - } - return this -} - -/** - * The external emit using Function.apply, used - * by Vue instance event methods. - * - * @param {Object} event - * @return {Emitter} - */ - -p.applyEmit = function (event) { - this._cancelled = false - this._cbs = this._cbs || {} - var callbacks = this._cbs[event] - if (callbacks) { - // avoid leaking arguments: - // http://jsperf.com/closure-with-arguments - var i = arguments.length - 1 - var args = new Array(i) - while (i--) { - args[i] = arguments[i + 1] - } - callbacks = _.toArray(callbacks) - i = 0 - for (var l = callbacks.length; i < l; i++) { - if (callbacks[i].apply(this._ctx, args) === false) { - this._cancelled = true - } - } - } - return this -} - -module.exports = Emitter \ No newline at end of file diff --git a/src/instance/events.js b/src/instance/events.js index f1df359f2..609de8b76 100644 --- a/src/instance/events.js +++ b/src/instance/events.js @@ -8,7 +8,6 @@ var inDoc = require('../util').inDoc exports._initEvents = function () { var options = this.$options - var emitter = this._emitter var events = options.events var methods = options.methods if (events) { @@ -19,7 +18,7 @@ exports._initEvents = function () { var handler = typeof handlers[i] === 'string' ? methods && methods[handlers[i]] : handlers[i] - emitter.on(e, handler) + this.$on(e, handler) } } } @@ -30,8 +29,7 @@ exports._initEvents = function () { */ exports._initDOMHooks = function () { - var emitter = this._emitter - emitter.on('hook:attached', function () { + this.$on('hook:attached', function () { this._isAttached = true var children = this._children if (!children) return @@ -42,7 +40,7 @@ exports._initDOMHooks = function () { } } }) - emitter.on('hook:detached', function () { + this.$on('hook:detached', function () { this._isAttached = false var children = this._children if (!children) return @@ -68,5 +66,5 @@ exports._callHook = function (hook) { handlers[i].call(this) } } - this._emitter.emit('hook:' + hook) + this.$emit('hook:' + hook) } \ No newline at end of file diff --git a/src/instance/init.js b/src/instance/init.js index 2da45ed26..347788e5c 100644 --- a/src/instance/init.js +++ b/src/instance/init.js @@ -1,4 +1,3 @@ -var Emitter = require('../emitter') var mergeOptions = require('../util/merge-option') /** @@ -17,13 +16,17 @@ exports._init = function (options) { options = options || {} this.$el = null - this.$ = {} this.$root = this.$root || this - this._emitter = new Emitter(this) - this._watchers = Object.create(null) - this._userWatchers = Object.create(null) + this.$ = {} + this._watchers = {} + this._userWatchers = {} this._directives = [] + // events bookkeeping + this._events = {} + this._eventsCount = {} + this._eventCancelled = false + // block instance properties this._blockStart = this._blockEnd = null diff --git a/src/observer/index.js b/src/observer/index.js index 0452031a5..cfd4f4f2d 100644 --- a/src/observer/index.js +++ b/src/observer/index.js @@ -18,10 +18,9 @@ var OBJECT = 1 * object's property keys into getter/setters that * collect dependencies and dispatches updates. * - * @constructor - * @extends Emitter * @param {Array|Object} value * @param {Number} type + * @constructor */ function Observer (value, type) { @@ -190,7 +189,6 @@ p.convert = function (key, val) { binding.notify() } }) - return binding } /** @@ -209,8 +207,8 @@ p.convert = function (key, val) { p.tryRelease = function () { if (!this.parentCount && !this.vmCount) { var value = this.value - value.__ob__ = null if (_.isArray(value)) { + value.__ob__.bindings = null this.unobserveArray(value) } else { for (var key in value) { @@ -220,6 +218,7 @@ p.tryRelease = function () { _.define(value, key, val, true) } } + value.__ob__ = null } }