Why use hostSetText not setElementText when patching text nodes #13066
-
core/packages/runtime-core/src/renderer.ts
Lines 490 to 503 in 4fea167
core/packages/runtime-dom/src/nodeOps.ts
Lines 77 to 83 in 4fea167
I noticed that when this function is executed, if the n1 node exists, it can be determined that both n1 and n2 are of type Text. So why use hostSetText (which set nodeValue
in a browser environment) instead of setElementText (which set textContent
in a browser environment)?
I wrote a script to test the execution speed of both and found that using textContent to set text is faster. Therefore, I'm wondering if, assuming the code logic is correct, such a replacement could be made to speed up text patching. After making the replacement, I ran unit tests and no errors were reported. As for the e2e tests, it might be due to incorrect configuration on my part, so I haven't run them yet.
test script as follow, If there is an issue with my test, welcome advise~
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>nodeValue vs textContent Performance Benchmark</title> <style> body { font-family: 'Arial', sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; line-height: 1.6; } .container { margin-bottom: 30px; } .test-area { display: none; } table { width: 100%; border-collapse: collapse; margin: 20px 0; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } tr:nth-child(even) { background-color: #f9f9f9; } .winner { font-weight: bold; color: #2ecc71; } button { padding: 10px 15px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; margin-right: 10px; } button:hover { background-color: #45a049; } #results { margin-top: 20px; } .chart-container { height: 300px; margin: 20px 0; } .legend { display: flex; margin-bottom: 10px; } .legend-item { margin-right: 20px; display: flex; align-items: center; } .legend-color { width: 20px; height: 20px; margin-right: 5px; } .nodeValue-color { background-color: #3498db; } .textContent-color { background-color: #e74c3c; } </style> </head> <body> <h1>nodeValue vs textContent Performance Benchmark</h1> <p>This test compares the performance difference between using <code>nodeValue</code> and <code>textContent</code> when modifying DOM text nodes.</p> <div class="container"> <h2>Test Configuration</h2> <div> <label for="iterations">Iterations: </label> <input type="number" id="iterations" value="5000" min="1000" max="50000" step="1000"> </div> <div> <label for="warmup">Warmup Cycles: </label> <input type="number" id="warmup" value="1000" min="0" max="10000" step="100"> </div> <div> <label for="testRuns">Test Runs: </label> <input type="number" id="testRuns" value="5" min="1" max="20"> </div> <button id="runTests">Run Test</button> <button id="resetResults">Reset Results</button> </div> <!-- Test Area --> <div id="testArea" class="test-area"> <!-- Single Text Node --> <div id="singleTextNode">This is a single text node</div> </div> <!-- Results Area --> <div id="results"> <h2>Test Results</h2> <div id="summaryResults"></div> <div class="chart-container"> <div class="legend"> <div class="legend-item"> <div class="legend-color nodeValue-color"></div> <span>nodeValue</span> </div> <div class="legend-item"> <div class="legend-color textContent-color"></div> <span>textContent</span> </div> </div> <canvas id="resultsChart"></canvas> </div> <h3>Detailed Results</h3> <div id="detailedResults"></div> </div> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script> // Test configuration const config = { get iterations() { return parseInt(document.getElementById('iterations').value) }, get warmup() { return parseInt(document.getElementById('warmup').value) }, get testRuns() { return parseInt(document.getElementById('testRuns').value) } }; let resultsChart = null; // Warmup function to eliminate JIT compilation effects function warmup() { const node = document.createTextNode(''); const content = 'warmup'; for (let i = 0; i < config.warmup; i++) { node.nodeValue = content + i; node.textContent = content + i; } } // Test case: Single text node function testSingleTextNode() { const container = document.getElementById('singleTextNode'); const textNode = container.firstChild; const baseText = 'This is a single text node'; // Test nodeValue const nodeValueResults = []; for (let run = 0; run < config.testRuns; run++) { const start = performance.now(); for (let i = 0; i < config.iterations; i++) { textNode.nodeValue = baseText + i; } const end = performance.now(); nodeValueResults.push(end - start); } // Reset content textNode.nodeValue = baseText; // Test textContent const textContentResults = []; for (let run = 0; run < config.testRuns; run++) { const start = performance.now(); for (let i = 0; i < config.iterations; i++) { textNode.textContent = baseText + i; } const end = performance.now(); textContentResults.push(end - start); } // Reset content textNode.textContent = baseText; return { nodeValue: { results: nodeValueResults, avg: nodeValueResults.reduce((a, b) => a + b, 0) / nodeValueResults.length }, textContent: { results: textContentResults, avg: textContentResults.reduce((a, b) => a + b, 0) / textContentResults.length } }; } // Run the test function runTest() { // Clear old results document.getElementById('detailedResults').innerHTML = ''; document.getElementById('summaryResults').innerHTML = ''; // Warmup console.log('Warming up...'); warmup(); console.log('Starting test...'); // Run test const results = { singleTextNode: testSingleTextNode() }; // Display results displayResults(results); console.log('Test completed!'); return results; } // Display results function displayResults(results) { const detailedResults = document.getElementById('detailedResults'); const summaryResults = document.getElementById('summaryResults'); // Create summary table let summaryHtml = ` <table> <tr> <th>Test Case</th> <th>nodeValue Avg Time (ms)</th> <th>textContent Avg Time (ms)</th> <th>Performance Difference</th> <th>Faster Method</th> </tr> `; const chartData = { labels: [], nodeValueData: [], textContentData: [] }; // Process results for (const [testName, testResults] of Object.entries(results)) { let testNameDisplay = 'Single Text Node'; chartData.labels.push(testNameDisplay); chartData.nodeValueData.push(testResults.nodeValue.avg); chartData.textContentData.push(testResults.textContent.avg); const nodeValueAvg = testResults.nodeValue.avg.toFixed(2); const textContentAvg = testResults.textContent.avg.toFixed(2); // Calculate performance difference percentage const faster = nodeValueAvg < textContentAvg ? 'nodeValue' : 'textContent'; const diff = Math.abs(nodeValueAvg - textContentAvg); const percentDiff = ((diff / Math.max(nodeValueAvg, textContentAvg)) * 100).toFixed(2); summaryHtml += ` <tr> <td>${testNameDisplay}</td> <td class="${faster === 'nodeValue' ? 'winner' : ''}">${nodeValueAvg}</td> <td class="${faster === 'textContent' ? 'winner' : ''}">${textContentAvg}</td> <td>${percentDiff}%</td> <td class="winner">${faster}</td> </tr> `; // Detailed results let detailedHtml = ` <h4>${testNameDisplay}</h4> <table> <tr> <th>Run</th> <th>nodeValue (ms)</th> <th>textContent (ms)</th> </tr> `; for (let i = 0; i < testResults.nodeValue.results.length; i++) { detailedHtml += ` <tr> <td>Run ${i + 1}</td> <td>${testResults.nodeValue.results[i].toFixed(2)}</td> <td>${testResults.textContent.results[i].toFixed(2)}</td> </tr> `; } detailedHtml += ` <tr> <td><strong>Average</strong></td> <td><strong>${nodeValueAvg}</strong></td> <td><strong>${textContentAvg}</strong></td> </tr> </table> `; detailedResults.innerHTML += detailedHtml; } summaryHtml += '</table>'; summaryResults.innerHTML = summaryHtml; // Draw chart updateChart(chartData); } // Update chart function updateChart(data) { const ctx = document.getElementById('resultsChart').getContext('2d'); if (resultsChart) { resultsChart.destroy(); } resultsChart = new Chart(ctx, { type: 'bar', data: { labels: data.labels, datasets: [ { label: 'nodeValue Execution Time (ms)', data: data.nodeValueData, backgroundColor: '#3498db', borderColor: '#2980b9', borderWidth: 1 }, { label: 'textContent Execution Time (ms)', data: data.textContentData, backgroundColor: '#e74c3c', borderColor: '#c0392b', borderWidth: 1 } ] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, title: { display: true, text: 'Execution Time (ms)' } } } } }); } // Event listeners document.getElementById('runTests').addEventListener('click', runTest); document.getElementById('resetResults').addEventListener('click', () => { document.getElementById('detailedResults').innerHTML = ''; document.getElementById('summaryResults').innerHTML = ''; if (resultsChart) { resultsChart.destroy(); resultsChart = null; } }); document.addEventListener('DOMContentLoaded', () => { // Initialize page const testArea = document.getElementById('testArea'); testArea.style.display = 'block'; }); </script> </body> </html>
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment
-
const el = document.createElement('span') const text = document.createTextNode('hi') el.appendChild(text) el.textContent = 'foo' // will re-create a text node text.nodeValue = 'foo' // re-using the text node
Which one do you think is better?
Beta Was this translation helpful? Give feedback.