Skip to content

Commit 29b95b1

Browse files
committed
perf: Optimize DOM hot path DeltaUpdates to reduce GC pressure (#9317)
1 parent 73b48c2 commit 29b95b1

2 files changed

Lines changed: 65 additions & 65 deletions

File tree

src/main/DeltaUpdates.mjs

Lines changed: 59 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,22 @@ class DeltaUpdates extends Base {
266266
if (!parentNode) return null;
267267

268268
const
269-
isComment = Node.COMMENT_NODE,
270-
startStr = ` ${id}-start `;
271-
272-
let startNode = Array.from(parentNode.childNodes).find(n =>
273-
n.nodeType === isComment && n.nodeValue === startStr
274-
);
269+
isComment = Node.COMMENT_NODE,
270+
startStr = ` ${id}-start `,
271+
childNodes = parentNode.childNodes;
272+
273+
let startNode = null,
274+
i = 0,
275+
len = childNodes.length,
276+
n;
277+
278+
for (; i < len; i++) {
279+
n = childNodes[i];
280+
if (n.nodeType === isComment && n.nodeValue === startStr) {
281+
startNode = n;
282+
break
283+
}
284+
}
275285

276286
// Fallback for text nodes (if we want to unify, though text nodes use ` id `)
277287
// For now, let's strictly handle Fragments here.
@@ -578,47 +588,6 @@ class DeltaUpdates extends Base {
578588
}
579589
}
580590

581-
/**
582-
* Helper to retrieve all DOM nodes belonging to a Fragment (or Text Node range).
583-
* @param {HTMLElement} parentNode
584-
* @param {String} id
585-
* @returns {Object|null} {startNode, endNode, nodes: []}
586-
*/
587-
getFragmentNodes(parentNode, id) {
588-
if (!parentNode) return null;
589-
590-
const
591-
isComment = Node.COMMENT_NODE,
592-
startStr = ` ${id}-start `;
593-
594-
let startNode = Array.from(parentNode.childNodes).find(n =>
595-
n.nodeType === isComment && n.nodeValue === startStr
596-
);
597-
598-
// Fallback for text nodes (if we want to unify, though text nodes use ` id `)
599-
// For now, let's strictly handle Fragments here.
600-
601-
if (!startNode) return null;
602-
603-
const
604-
endStr = ` ${id}-end `,
605-
nodes = [];
606-
607-
let currentNode = startNode.nextSibling;
608-
609-
while (currentNode) {
610-
// Check if we hit the end anchor
611-
if (currentNode.nodeType === isComment && currentNode.nodeValue === endStr) {
612-
return {startNode, endNode: currentNode, nodes};
613-
}
614-
615-
nodes.push(currentNode);
616-
currentNode = currentNode.nextSibling
617-
}
618-
619-
return null // End anchor not found (should not happen in healthy DOM)
620-
}
621-
622591
/**
623592
* Clears all child nodes of a given parent DOM node.
624593
* This is achieved by setting its `innerHTML` property to an empty string,
@@ -667,12 +636,24 @@ class DeltaUpdates extends Base {
667636
}
668637

669638
// 2. Text Node Logic
670-
const isComment = Node.COMMENT_NODE;
639+
const
640+
isComment = Node.COMMENT_NODE,
641+
searchStr = ` ${id} `,
642+
childNodes = parentNode.childNodes;
643+
644+
let startComment = null,
645+
i = 0,
646+
len = childNodes.length,
647+
n;
671648

672649
// Find the starting comment node using its id marker
673-
const startComment = Array.from(parentNode.childNodes).find(n =>
674-
n.nodeType === isComment && n.nodeValue.includes(` ${id} `)
675-
);
650+
for (; i < len; i++) {
651+
n = childNodes[i];
652+
if (n.nodeType === isComment && n.nodeValue.includes(searchStr)) {
653+
startComment = n;
654+
break
655+
}
656+
}
676657

677658
if (startComment) {
678659
const
@@ -726,13 +707,18 @@ class DeltaUpdates extends Base {
726707
*/
727708
updateNode(delta) {
728709
let me = this,
729-
node = DomAccess.getElementOrBody(delta.id);
710+
node = DomAccess.getElementOrBody(delta.id),
711+
key, prop, val, value;
730712

731713
if (node) {
732-
Object.entries(delta).forEach(([prop, value]) => {
714+
for (prop in delta) {
715+
value = delta[prop];
716+
733717
switch (prop) {
734718
case 'attributes':
735-
Object.entries(value).forEach(([key, val]) => {
719+
for (key in value) {
720+
val = value[key];
721+
736722
if (voidAttributes.has(key)) {
737723
node[key] = val === 'true' // vnode attribute values get converted into strings
738724
} else if (val === null || val === '') {
@@ -753,7 +739,7 @@ class DeltaUpdates extends Base {
753739
node.setAttribute(key, val)
754740
}
755741
}
756-
});
742+
}
757743
break
758744
case 'cls':
759745
value.add && node.classList.add(...value.add);
@@ -774,7 +760,8 @@ class DeltaUpdates extends Base {
774760
break
775761
case 'style':
776762
if (Neo.isObject(value)) {
777-
Object.entries(value).forEach(([key, val]) => {
763+
for (key in value) {
764+
val = value[key];
778765
let important;
779766

780767
if (Neo.isString(val) && val.includes('!important')) {
@@ -783,14 +770,14 @@ class DeltaUpdates extends Base {
783770
}
784771

785772
node.style.setProperty(Neo.decamel(key), val, important)
786-
})
773+
}
787774
}
788775
break
789776
case 'textContent':
790777
node.textContent = value;
791778
break
792779
}
793-
})
780+
}
794781
}
795782
}
796783

@@ -813,9 +800,20 @@ class DeltaUpdates extends Base {
813800
idString = ` ${id} `;
814801

815802
if (node) {
816-
const startComment = Array.from(node.childNodes).find(n =>
817-
n.nodeType === isComment && n.nodeValue === idString
818-
);
803+
const childNodes = node.childNodes;
804+
805+
let startComment = null,
806+
i = 0,
807+
len = childNodes.length,
808+
n;
809+
810+
for (; i < len; i++) {
811+
n = childNodes[i];
812+
if (n.nodeType === isComment && n.nodeValue === idString) {
813+
startComment = n;
814+
break
815+
}
816+
}
819817

820818
if (startComment?.nextSibling) {
821819
startComment.nextSibling.nodeValue = value

src/main/render/DomApiRenderer.mjs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,16 @@ const DomApiRenderer = {
6161
domNode[Neo.config.useDomIds ? 'id' : 'data-neo-id'] = vnode.id;
6262

6363
// Apply Attributes
64-
Object.entries(vnode.attributes).forEach(([key, value]) => {
64+
for (const key in vnode.attributes) {
65+
const value = vnode.attributes[key];
6566
if (voidAttributes.has(key)) {
6667
domNode.toggleAttribute(key, value === 'true' || value === true)
6768
} else if (key === 'value') {
6869
domNode.value = value
6970
} else if (value !== null && value !== undefined) {
7071
domNode.setAttribute(key, value)
7172
}
72-
});
73+
}
7374

7475
// Apply Classes
7576
if (vnode.className.length > 0) {
@@ -78,7 +79,8 @@ const DomApiRenderer = {
7879

7980
// Apply Styles
8081
if (Neo.isObject(vnode.style)) {
81-
Object.entries(vnode.style).forEach(([key, value]) => {
82+
for (const key in vnode.style) {
83+
let value = vnode.style[key];
8284
let important;
8385

8486
if (Neo.isString(value) && value.includes('!important')) {
@@ -88,7 +90,7 @@ const DomApiRenderer = {
8890
}
8991

9092
domNode.style.setProperty(Neo.decamel(key), value, important)
91-
})
93+
}
9294
}
9395

9496
// Handle innerHTML & textContent

0 commit comments

Comments
 (0)