From c660917b29ed8c1994b497937ea67c80b56a8be2 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 13 Jan 2017 19:44:32 -0500 Subject: [PATCH] improve event update algorithm (thanks to suggestions from @defcc) --- src/core/vdom/helpers/update-listeners.js | 96 +++++++++---------- test/unit/modules/vdom/modules/events.spec.js | 12 ++- 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/core/vdom/helpers/update-listeners.js b/src/core/vdom/helpers/update-listeners.js index 674a7cab..2af4a0c7 100644 --- a/src/core/vdom/helpers/update-listeners.js +++ b/src/core/vdom/helpers/update-listeners.js @@ -1,7 +1,44 @@ /* @flow */ +import { cached } from 'shared/util' import { warn } from 'core/util/index' +const normalizeEvent = cached((name: string): { + name: string, + once: boolean, + capture: boolean +} => { + const once = name.charAt(0) === '~' // Prefixed last, checked first + name = once ? name.slice(1) : name + const capture = name.charAt(0) === '!' + name = capture ? name.slice(1) : name + return { + name, + once, + capture + } +}) + +function createEventHandle (fn: Function | Array): { + fn: Function | Array; + invoker: Function; +} { + const handle = { + fn, + invoker: function () { + const fn = handle.fn + if (Array.isArray(fn)) { + for (let i = 0; i < fn.length; i++) { + fn[i].apply(null, arguments) + } + } else { + fn.apply(null, arguments) + } + } + } + return handle +} + export function updateListeners ( on: Object, oldOn: Object, @@ -9,69 +46,30 @@ export function updateListeners ( remove: Function, vm: Component ) { - let name, cur, old, curIsArray, oldIsArray, fn, event, capture, once + let name, cur, old, event for (name in on) { cur = on[name] old = oldOn[name] - curIsArray = Array.isArray(cur) - oldIsArray = Array.isArray(old) + event = normalizeEvent(name) if (!cur) { process.env.NODE_ENV !== 'production' && warn( - `Invalid handler for event "${name}": got ` + String(cur), + `Invalid handler for event "${event.name}": got ` + String(cur), vm ) } else if (!old) { - once = name.charAt(0) === '~' // Prefixed last, checked first - event = once ? name.slice(1) : name - capture = event.charAt(0) === '!' - event = capture ? event.slice(1) : event - if (curIsArray) { - add(event, (cur.invoker = arrInvoker(cur)), once, capture) - } else { - if (!cur.invoker) { - fn = cur - cur = on[name] = {} - cur.fn = fn - cur.invoker = fnInvoker(cur) - } - add(event, cur.invoker, once, capture) + if (!cur.invoker) { + cur = on[name] = createEventHandle(cur) } + add(event.name, cur.invoker, event.once, event.capture) } else if (cur !== old) { - if (curIsArray || oldIsArray) { - if (!curIsArray) cur = [cur] - if (!oldIsArray) old = [old] - old.length = cur.length - for (let i = 0; i < old.length; i++) old[i] = cur[i] - on[name] = old - } else { - old.fn = cur - on[name] = old - } + old.fn = cur + on[name] = old } } for (name in oldOn) { if (!on[name]) { - once = name.charAt(0) === '~' // Prefixed last, checked first - event = once ? name.slice(1) : name - capture = event.charAt(0) === '!' - event = capture ? event.slice(1) : event - remove(event, oldOn[name].invoker, capture) - } - } -} - -function arrInvoker (arr: Array): Function { - return function (ev) { - const single = arguments.length === 1 - for (let i = 0; i < arr.length; i++) { - single ? arr[i](ev) : arr[i].apply(null, arguments) + event = normalizeEvent(name) + remove(event.name, oldOn[name].invoker, event.capture) } } } - -function fnInvoker (o: { fn: Function }): Function { - return function (ev) { - const single = arguments.length === 1 - single ? o.fn(ev) : o.fn.apply(null, arguments) - } -} diff --git a/test/unit/modules/vdom/modules/events.spec.js b/test/unit/modules/vdom/modules/events.spec.js index 1da758f1..9dcd72c4 100644 --- a/test/unit/modules/vdom/modules/events.spec.js +++ b/test/unit/modules/vdom/modules/events.spec.js @@ -105,24 +105,30 @@ describe('vdom events module', () => { const click = jasmine.createSpy() const click2 = jasmine.createSpy() const click3 = jasmine.createSpy() + const vnode0 = new VNode('a', { on: { click: click }}) const vnode1 = new VNode('a', { on: { click: [click, click2] }}) const vnode2 = new VNode('a', { on: { click: click }}) const vnode3 = new VNode('a', { on: { click: [click2, click3] }}) - const elm = patch(null, vnode1) + const elm = patch(null, vnode0) document.body.appendChild(elm) triggerEvent(elm, 'click') expect(click.calls.count()).toBe(1) + expect(click2.calls.count()).toBe(0) + + patch(vnode0, vnode1) + triggerEvent(elm, 'click') + expect(click.calls.count()).toBe(2) expect(click2.calls.count()).toBe(1) patch(vnode1, vnode2) triggerEvent(elm, 'click') - expect(click.calls.count()).toBe(2) + expect(click.calls.count()).toBe(3) expect(click2.calls.count()).toBe(1) patch(vnode2, vnode3) triggerEvent(elm, 'click') - expect(click.calls.count()).toBe(2) + expect(click.calls.count()).toBe(3) expect(click2.calls.count()).toBe(2) expect(click3.calls.count()).toBe(1) })