From 687db9f99bae490cceb8ebf4b38d96167689f502 Mon Sep 17 00:00:00 2001 From: Caridy Date: Wed, 11 Jan 2017 13:23:30 -0500 Subject: [PATCH] [perf-optimization] skip unnecessary work in modules that can support memoization of the data structure consumed by them --- src/modules/attributes.ts | 1 + src/modules/class.ts | 1 + src/modules/dataset.ts | 1 + src/modules/props.ts | 1 + src/modules/style.ts | 1 + test/attributes.js | 13 +++++++++++++ test/core.js | 24 +++++++++++++++++++++++- test/dataset.js | 11 +++++++++++ test/style.js | 11 +++++++++++ 9 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/modules/attributes.ts b/src/modules/attributes.ts index 3350f70..0e29e8f 100755 --- a/src/modules/attributes.ts +++ b/src/modules/attributes.ts @@ -23,6 +23,7 @@ function updateAttrs(oldVnode: VNode, vnode: VNode): void { attrs = (vnode.data as VNodeData).attrs, namespaceSplit: Array; if (!oldAttrs && !attrs) return; + if (oldAttrs === attrs) return; oldAttrs = oldAttrs || {}; attrs = attrs || {}; diff --git a/src/modules/class.ts b/src/modules/class.ts index aeaff83..2febf38 100755 --- a/src/modules/class.ts +++ b/src/modules/class.ts @@ -7,6 +7,7 @@ function updateClass(oldVnode: VNode, vnode: VNode): void { klass = (vnode.data as VNodeData).class; if (!oldClass && !klass) return; + if (oldClass === klass) return; oldClass = oldClass || {}; klass = klass || {}; diff --git a/src/modules/dataset.ts b/src/modules/dataset.ts index 352bf0b..d6643bb 100755 --- a/src/modules/dataset.ts +++ b/src/modules/dataset.ts @@ -8,6 +8,7 @@ function updateDataset(oldVnode: VNode, vnode: VNode): void { key: string; if (!oldDataset && !dataset) return; + if (oldDataset === dataset) return; oldDataset = oldDataset || {}; dataset = dataset || {}; diff --git a/src/modules/props.ts b/src/modules/props.ts index 8d59885..b149d0e 100755 --- a/src/modules/props.ts +++ b/src/modules/props.ts @@ -7,6 +7,7 @@ function updateProps(oldVnode: VNode, vnode: VNode): void { props = (vnode.data as VNodeData).props; if (!oldProps && !props) return; + if (oldProps === props) return; oldProps = oldProps || {}; props = props || {}; diff --git a/src/modules/style.ts b/src/modules/style.ts index d248efe..86d5e05 100755 --- a/src/modules/style.ts +++ b/src/modules/style.ts @@ -14,6 +14,7 @@ function updateStyle(oldVnode: VNode, vnode: VNode): void { style = (vnode.data as VNodeData).style; if (!oldStyle && !style) return; + if (oldStyle === style) return; oldStyle = oldStyle || {}; style = style || {}; var oldHasDel = 'delayed' in oldStyle; diff --git a/test/attributes.js b/test/attributes.js index f032318..0d14751 100644 --- a/test/attributes.js +++ b/test/attributes.js @@ -19,6 +19,19 @@ describe('attributes', function() { assert.strictEqual(elm.getAttribute('minlength'), '1'); assert.strictEqual(elm.getAttribute('value'), 'true'); }); + it('can be memoized', function() { + var cachedAttrs = {href: '/foo', minlength: 1, value: true}; + var vnode1 = h('div', {attrs: cachedAttrs}); + var vnode2 = h('div', {attrs: cachedAttrs}); + elm = patch(vnode0, vnode1).elm; + assert.strictEqual(elm.getAttribute('href'), '/foo'); + assert.strictEqual(elm.getAttribute('minlength'), '1'); + assert.strictEqual(elm.getAttribute('value'), 'true'); + elm = patch(vnode1, vnode2).elm; + assert.strictEqual(elm.getAttribute('href'), '/foo'); + assert.strictEqual(elm.getAttribute('minlength'), '1'); + assert.strictEqual(elm.getAttribute('value'), 'true'); + }); it('are not omitted when falsy values are provided', function() { var vnode1 = h('div', {attrs: {href: null, minlength: 0, value: false}}); elm = patch(vnode0, vnode1).elm; diff --git a/test/core.js b/test/core.js index 2a52558..13435b4 100644 --- a/test/core.js +++ b/test/core.js @@ -166,7 +166,7 @@ describe('snabbdom', function() { assert.equal(elm.className, 'class'); }); }); - describe('pathing an element', function() { + describe('patching an element', function() { it('changes the elements classes', function() { var vnode1 = h('i', {class: {i: true, am: true, horse: true}}); var vnode2 = h('i', {class: {i: true, am: true, horse: false}}); @@ -185,6 +185,19 @@ describe('snabbdom', function() { assert(elm.classList.contains('am')); assert(!elm.classList.contains('horse')); }); + it('preserves memoized classes', function() { + var cachedClass = {i: true, am: true, horse: false}; + var vnode1 = h('i', {class: cachedClass}); + var vnode2 = h('i', {class: cachedClass}); + elm = patch(vnode0, vnode1).elm; + assert(elm.classList.contains('i')); + assert(elm.classList.contains('am')); + assert(!elm.classList.contains('horse')); + elm = patch(vnode1, vnode2).elm; + assert(elm.classList.contains('i')); + assert(elm.classList.contains('am')); + assert(!elm.classList.contains('horse')); + }); it('removes missing classes', function() { var vnode1 = h('i', {class: {i: true, am: true, horse: true}}); var vnode2 = h('i', {class: {i: true, am: true}}); @@ -201,6 +214,15 @@ describe('snabbdom', function() { elm = patch(vnode1, vnode2).elm; assert.equal(elm.src, 'http://localhost/'); }); + it('preserves memoized props', function() { + var cachedProps = {src: 'http://other/'}; + var vnode1 = h('a', {props: cachedProps}); + var vnode2 = h('a', {props: cachedProps}); + elm = patch(vnode0, vnode1).elm; + assert.equal(elm.src, 'http://other/'); + elm = patch(vnode1, vnode2).elm; + assert.equal(elm.src, 'http://other/'); + }); it('removes an elements props', function() { var vnode1 = h('a', {props: {src: 'http://other/'}}); var vnode2 = h('a'); diff --git a/test/dataset.js b/test/dataset.js index 77e9a0d..fe72ec4 100644 --- a/test/dataset.js +++ b/test/dataset.js @@ -28,6 +28,17 @@ describe('dataset', function() { assert.equal(elm.dataset.baz, 'baz'); assert.equal(elm.dataset.foo, undefined); }); + it('can be memoized', function() { + var cachedDataset = {foo: 'foo', bar: 'bar'}; + var vnode1 = h('i', {dataset: cachedDataset}); + var vnode2 = h('i', {dataset: cachedDataset}); + elm = patch(vnode0, vnode1).elm; + assert.equal(elm.dataset.foo, 'foo'); + assert.equal(elm.dataset.bar, 'bar'); + elm = patch(vnode1, vnode2).elm; + assert.equal(elm.dataset.foo, 'foo'); + assert.equal(elm.dataset.bar, 'bar'); + }); it('handles string conversions', function() { var vnode1 = h('i', {dataset: {empty: '', dash: '-', dashed:'foo-bar', camel: 'fooBar', integer:0, float:0.1}}); elm = patch(vnode0, vnode1).elm; diff --git a/test/style.js b/test/style.js index c75d3a4..3e9f7ba 100644 --- a/test/style.js +++ b/test/style.js @@ -18,6 +18,17 @@ describe('style', function() { elm = patch(vnode0, h('div', {style: {fontSize: '12px'}})).elm; assert.equal(elm.style.fontSize, '12px'); }); + it('can be memoized', function() { + var cachedStyles = {fontSize: '14px', display: 'inline'}; + var vnode1 = h('i', {style: cachedStyles}); + var vnode2 = h('i', {style: cachedStyles}); + elm = patch(vnode0, vnode1).elm; + assert.equal(elm.style.fontSize, '14px'); + assert.equal(elm.style.display, 'inline'); + elm = patch(vnode1, vnode2).elm; + assert.equal(elm.style.fontSize, '14px'); + assert.equal(elm.style.display, 'inline'); + }); it('updates styles', function() { var vnode1 = h('i', {style: {fontSize: '14px', display: 'inline'}}); var vnode2 = h('i', {style: {fontSize: '12px', display: 'block'}});