Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Why use hostSetText not setElementText when patching text nodes #13066

Unanswered
shellRaining asked this question in Help/Questions
Discussion options

const processText: ProcessTextOrCommentFn = (n1, n2, container, anchor) => {
if (n1 == null) {
hostInsert(
(n2.el = hostCreateText(n2.children as string)),
container,
anchor,
)
} else {
const el = (n2.el = n1.el!)
if (n2.children !== n1.children) {
hostSetText(el, n2.children as string)
}
}
}
setText: (node, text) => {
node.nodeValue = text
},
setElementText: (el, text) => {
el.textContent = text
},

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>
You must be logged in to vote

Replies: 1 comment

Comment options

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?

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /