Virtual DOM

What is Virtual DOM?

The Virtual DOM is a lightweight JavaScript representation of the Real DOM. It's an abstraction that allows frameworks to optimize DOM updates by calculating the minimal set of changes needed.

Try Virtual DOM Concept

Code Editor
Console Output
Click "Run" to execute your code...

Example 1: Virtual DOM Concept

// Virtual DOM is a JavaScript object representation of the DOM
// Example structure:

const virtualDOM = {
  type: 'div',
  props: {
    id: 'container',
    className: 'main'
  },
  children: [
    {
      type: 'h1',
      props: {className: 'title'},
      children: ['Hello World']
    },
    {
      type: 'p',
      props: {},
      children: ['This is a paragraph']
    }
  ]
};

// This represents:
// <div id="container" class="main">
//   <h1 class="title">Hello World</h1>
//   <p>This is a paragraph</p>
// </div>

// In React JSX:
// const element = (
//   <div id="container" className="main">
//     <h1 className="title">Hello World</h1>
//     <p>This is a paragraph</p>
//   </div>
// );

// React.createElement creates the virtual DOM object:
const reactElement = React.createElement(
  'div',
  { id: 'container', className: 'main' },
  React.createElement('h1', { className: 'title' }, 'Hello World'),
  React.createElement('p', null, 'This is a paragraph')
);

Example 2: Simple Virtual DOM Implementation

// Creating virtual DOM elements
function createElement(type, props, ...children) {
  return {
    type,
    props: props || {},
    children: children.flat()
  };
}

// Rendering virtual DOM to real DOM
function render(vNode) {
  // Handle text nodes
  if (typeof vNode === 'string') {
    return document.createTextNode(vNode);
  }
  
  // Create element
  const element = document.createElement(vNode.type);
  
  // Set props
  Object.keys(vNode.props).forEach(key => {
    if (key === 'className') {
      element.className = vNode.props[key];
    } else if (key.startsWith('on')) {
      // Handle events: onClick -> click
      const event = key.substring(2).toLowerCase();
      element.addEventListener(event, vNode.props[key]);
    } else {
      element.setAttribute(key, vNode.props[key]);
    }
  });
  
  // Render children
  vNode.children.forEach(child => {
    element.appendChild(render(child));
  });
  
  return element;
}

// Usage
const vApp = createElement(
  'div',
  { id: 'app', className: 'container' },
  createElement('h1', {}, 'Virtual DOM Example'),
  createElement('p', {}, 'This is rendered from virtual DOM'),
  createElement('button', 
    { onClick: () => console.log('Clicked!') }, 
    'Click Me'
  )
);

// Render to real DOM
// const app = render(vApp);
// document.body.appendChild(app);

Example 3: Diffing Algorithm Concept

// Simple diff algorithm to find changes
function diff(oldVNode, newVNode) {
  // Different types - replace
  if (oldVNode.type !== newVNode.type) {
    return { type: 'REPLACE', newVNode };
  }
  
  // Text node changed
  if (typeof oldVNode === 'string' && oldVNode !== newVNode) {
    return { type: 'TEXT', newVNode };
  }
  
  // Check props changes
  const propsPatches = diffProps(oldVNode.props, newVNode.props);
  
  // Check children changes
  const childrenPatches = [];
  const maxLength = Math.max(
    oldVNode.children.length,
    newVNode.children.length
  );
  
  for (let i = 0; i < maxLength; i++) {
    childrenPatches.push(
      diff(oldVNode.children[i], newVNode.children[i])
    );
  }
  
  return {
    type: 'UPDATE',
    propsPatches,
    childrenPatches
  };
}

function diffProps(oldProps, newProps) {
  const patches = [];
  
  // Find removed or changed props
  Object.keys(oldProps).forEach(key => {
    if (!(key in newProps)) {
      patches.push({ type: 'REMOVE', key });
    } else if (oldProps[key] !== newProps[key]) {
      patches.push({ type: 'UPDATE', key, value: newProps[key] });
    }
  });
  
  // Find added props
  Object.keys(newProps).forEach(key => {
    if (!(key in oldProps)) {
      patches.push({ type: 'ADD', key, value: newProps[key] });
    }
  });
  
  return patches;
}

// Example usage
const oldTree = {
  type: 'div',
  props: { className: 'old' },
  children: [
    { type: 'h1', props: {}, children: ['Old Title'] }
  ]
};

const newTree = {
  type: 'div',
  props: { className: 'new' },
  children: [
    { type: 'h1', props: {}, children: ['New Title'] },
    { type: 'p', props: {}, children: ['New paragraph'] }
  ]
};

const patches = diff(oldTree, newTree);
console.log('Changes:', patches);

Example 4: Virtual DOM Benefits

// Without Virtual DOM (Direct DOM manipulation)
function updateWithoutVirtualDOM(items) {
  const list = document.getElementById('list');
  
  // Clear and rebuild entire list (inefficient!)
  list.innerHTML = '';
  
  items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    list.appendChild(li);
  });
}

// With Virtual DOM (React-like)
function updateWithVirtualDOM(items) {
  // Create virtual representation
  const virtualList = {
    type: 'ul',
    props: { id: 'list' },
    children: items.map(item => ({
      type: 'li',
      props: {},
      children: [item]
    }))
  };
  
  // React does this internally:
  // 1. Compare old virtual DOM with new virtual DOM
  // 2. Calculate minimal changes (diff)
  // 3. Apply only those changes to real DOM
  
  return virtualList;
}

// Performance comparison
console.time('Without Virtual DOM');
for (let i = 0; i < 100; i++) {
  updateWithoutVirtualDOM(['item1', 'item2', 'item3']);
}
console.timeEnd('Without Virtual DOM');

// Virtual DOM frameworks are faster because:
// 1. Batch DOM updates
// 2. Minimize reflows and repaints
// 3. Only update what changed
// 4. Can prioritize updates (React Fiber)

// Example: Updating 1000 items
const largeList = Array.from({ length: 1000 }, (_, i) => `Item ${i}`);

// Direct DOM: Clears and rebuilds all 1000 items
// Virtual DOM: Only updates the items that changed

Example 5: React Virtual DOM in Action

// React component using Virtual DOM
function TodoList() {
  const [todos, setTodos] = React.useState([
    { id: 1, text: 'Learn React', done: false },
    { id: 2, text: 'Build app', done: false }
  ]);
  
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ));
  };
  
  // When state changes:
  // 1. React creates new virtual DOM
  // 2. Compares with previous virtual DOM (reconciliation)
  // 3. Calculates minimal changes
  // 4. Updates only changed parts in real DOM
  
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id} onClick={() => toggleTodo(todo.id)}>
          <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
            {todo.text}
          </span>
        </li>
      ))}
    </ul>
  );
}

// When you toggle a todo:
// - React creates new virtual DOM with updated todo
// - Diffs against old virtual DOM
// - Finds only that one <li> changed
// - Updates only that <li> in real DOM
// - Other <li> elements are not touched!

// Keys help React identify which items changed
// Bad (without keys):
// todos.map(todo => <li>{todo.text}</li>)
// React can't track which item is which

// Good (with keys):
// todos.map(todo => <li key={todo.id}>{todo.text}</li>)
// React knows exactly which item changed

// Virtual DOM enables:
// - Declarative UI (describe what you want, not how to build it)
// - Efficient updates (minimal DOM manipulation)
// - Cross-platform rendering (React Native uses same concept)
// - Time-travel debugging
// - Server-side rendering

Key Points

  • Virtual DOM is a JavaScript representation of the Real DOM
  • Enables efficient updates by calculating minimal changes (diffing)
  • Reduces expensive DOM operations (reflows, repaints)
  • Allows batch updates for better performance
  • Used by React, Vue, and other modern frameworks
  • Trade-off: Additional memory for virtual tree vs faster updates
  • Not always faster than direct DOM (depends on use case)