Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Didi Front-End Developer Interview Questions

Tech 2

First Round

  1. What is a closure? What are its practical applications?

A closure represents a function that retains access to variables from its outer scope, enabling communication between inner and outer function contexts. Common applications include timers, throttle functions, and debounce implemantations.

  1. Describe the event loop mechanism.

Synchronous tasks execute sequentially within the main thread, while asynchronous tasks are placed into task queues. Once all microtasks complete and the microtask queue becomes empty, the event loop proceeds to the next cycle, checking the macrotask queue for pending work.

  1. What is Virtual DOM? How does it work? What are its advantages and disadvantages?

Virtual DOM is a technique that uses JavaScript objects to represent actual DOM structures. The mechanism involves creating a virtual DOM tree to mirror the real DOM, then comparing the previous and current virtual DOM trees to identify differences. Only the changed portions are updated in the actual DOM.

Advantages include reduced DOM manipulation frequency, improved performance, and cross-platform rendering capabilities for both browser and server environments. Disadvantages involve additional memory overhead for storing virtual DOM objects and computational costs for diffing comparisons.

  1. What optimizations have Vue and React implemented to make virtual DOM diffing faster?

Key-based optimization involves assigning unique key attributes to virtual DOM nodes, enabling accurate comparison between old and new node relationships. Both frameworks employ bidirectional comparison algorithms that traverse from both ends simultaneously to identify the longest common subsequence. They also support asynchronous batched updates, queuing all DOM operations within a single event loop and executing them in the next cycle to minimize reflows and repaints.

  1. What is the purpose of keys in Vue and React? Why shouldn't index be used? What happens when index is used or when key is omitted?

Keys uniquely identify virtual DOM nodes, helping determine which nodes require updates, deletion, or insertion. Using index as a key causes problems when list items change: inserting an item in the middle shifts all subsequent items, causing them to receive different keys and triggering unnecessary re-renders. Omitting keys forces the framework to treat every node as entirely new, resulting in degraded performance.

  1. What is the principle behind Vue's two-way data binding?

Vue implements two-way binding through data observation combined with the publisher-subscriber pattern. When data changes, Vue automatically triggers view updates. Implementation uses Object.defineProperty to intercept property accessors, triggering getter and setter methods to monitor and update data.

  1. What is Vue's keep-alive component? How is it implemented? How does refreshing work?

Keep-alive is an abstract component that caches component instances. When wrapped components are toggled, they avoid repeated creation and destruction, improving performance.

The caching mechanism activates when cached components mount to the DOM and store themselves in cache. Upon leaving, components remain cached rather than being destroyed. When reactivated, components remount from cache, avoiding recreation overhead.

Keep-alive provides activated and deactivated lifecycle hooks for managing component state. The include and exclude props control which components get cached, while the max prop limits cache size.

Refresh occurs by looking up cached component instances using component keys. If found, the cached instance is reused; otherwise, a new instance is created.

  1. How does Vue parse templates? What does the template become?

The template compiler transforms templates into render functions, which generate VNode trees for dynamic rendering. Templates first parse into Abstract Syntax Trees (AST), then render functions use the VNode tree to generate actual DOM nodes, describing each node's type, properties, and children.

  1. How are directives, template variables, and HTML tags parsed?

Directives (v-bind, v-model, v-for) become VNode nodes added to the tree, generating corresponding DOM operations. Template variables like {{ message }} become VNode nodes, replaced with actual data during rendering. HTML tags parsing produces VNode nodes representing the DOM structure.

  1. Have you used Vue's render function? What is its relationship with templates?

Render functions generate VNode trees, offering manual control over component rendering for complex functionality. Both templates and render functions produce VNode trees for DOM generation. Templates provide readability, while render functions offer greater flexibility.

Vue automatically compiles templates into render functions, but developers can write render functions manually. Both approaches ultimately generate VNode trees for dynamic rendering.

  1. Implement a throttle function. How to ensure the last execution always runs?

Timestamp-based implementation:

function throttle(fn, delay) {
  let lastTimestamp = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTimestamp >= delay) {
      fn.apply(this, args);
      lastTimestamp = now;
    }
  };
}

Timer-based implementation:

function throttle(fn, delay) {
  let timerId = null;
  return function(...args) {
    if (!timerId) {
      timerId = setTimeout(() => {
        fn.apply(this, args);
        timerId = null;
      }, delay);
    }
  };
}

Ensuring last execution:

function throttleTrailing(fn, delay) {
  let timerId = null;
  return function(...args) {
    if (timerId) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      fn.apply(this, args);
      timerId = null;
    }, delay);
  };
}
  1. Implement a batch request function with concurrency control.
class TaskQueue {
  constructor() {
    this.items = [];
  }

  enqueue(value) {
    return this.items.push(value);
  }

  dequeue() {
    return this.items.shift();
  }

  isEmpty() {
    return this.items.length === 0;
  }
}

class PendingTask {
  constructor(resolver, fn, params) {
    this.resolver = resolver;
    this.fn = fn;
    this.params = params;
  }
}

class ConcurrencyPool {
  constructor(limit) {
    this.limit = limit;
    this.queue = new TaskQueue();
  }

  async addTask(fn, params) {
    return new Promise(resolve => {
      this.queue.enqueue(new PendingTask(resolve, fn, params));
      if (this.limit > 0) {
        this.limit--;
        const task = this.queue.dequeue();
        task.resolver(this.executeTask(task.fn, task.params));
      }
    });
  }

  processNext() {
    if (this.queue.isEmpty()) {
      return;
    }
    if (this.limit === 0) {
      return;
    }
    this.limit--;
    const task = this.queue.dequeue();
    task.resolver(this.executeTask(task.fn, task.params));
  }

  executeTask(fn, params) {
    const result = Promise.resolve(fn(...params));
    result.then(() => {
      this.limit++;
      this.processNext();
    }).catch(() => {
      this.limit++;
      this.processNext();
    });
    return result;
  }
}

const createTask = duration => new Promise(resolve => {
  console.log('Task completed:', duration);
  resolve(duration);
});

const tasks = [4000, 3000, 2000, 1300, 800, 2000];
const pool = new ConcurrencyPool(2);

async function runWithLimit() {
  console.time('EXECUTION_TIME');
  await Promise.all(tasks.map(item => pool.addTask(createTask, [item])));
  console.timeEnd('EXECUTION_TIME');
}

runWithLimit();

Second Round

  1. Convert array to tree structure.
const data = [
  { id: 2, name: 'Department B', parentId: 0 },
  { id: 3, name: 'Department C', parentId: 1 },
  { id: 1, name: 'Department A', parentId: 2 },
  { id: 4, name: 'Department D', parentId: 1 },
  { id: 5, name: 'Department E', parentId: 2 },
  { id: 6, name: 'Department F', parentId: 3 },
  { id: 7, name: 'Department G', parentId: 2 },
  { id: 8, name: 'Department H', parentId: 4 }
];

function buildTree(nodes, rootId) {
  const tree = [];
  for (const node of nodes) {
    if (node.parentId === rootId) {
      const children = buildTree(nodes, node.id);
      if (children.length > 0) {
        node.children = children;
      }
      tree.push(node);
    }
  }
  return tree;
}

Final Round

  1. Remove characters that appear least frequently from a string while preserving original order.
function removeRareChars(str) {
  const frequency = new Map();
  
  for (const char of str) {
    frequency.set(char, (frequency.get(char) || 0) + 1);
  }
  
  const minCount = Math.min(...frequency.values());
  let rareChar;
  
  for (const [key, value] of frequency.entries()) {
    if (value === minCount) {
      rareChar = key;
      break;
    }
  }
  
  return str.split('').filter(char => char !== rareChar).join('');
}
  1. Write a function to convert numbers into Chinese representations (supporting numbers up to 100 trillion).

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.