logo

每日前端手写题实战:深度解析Day2挑战

作者:狼烟四起2025.09.19 12:47浏览量:0

简介:本文聚焦"每日前端手写题--day2",通过手写实现防抖函数、深拷贝算法及虚拟DOM树生成三大核心功能,结合代码示例与性能优化策略,助力开发者掌握前端底层原理。

每日前端手写题实战:深度解析Day2挑战

一、防抖函数(Debounce)的手写实现

防抖是前端性能优化的核心技能之一,尤其在处理高频事件(如窗口resize、输入框联想)时能显著减少不必要的计算。Day2的挑战要求我们实现一个带立即执行选项的防抖函数。

1.1 基础防抖实现原理

防抖的核心思想是:在事件触发后,等待N毫秒再执行回调。若在等待期间再次触发,则重新计时。

  1. function debounce(func, delay) {
  2. let timer = null;
  3. return function(...args) {
  4. if (timer) clearTimeout(timer);
  5. timer = setTimeout(() => {
  6. func.apply(this, args);
  7. }, delay);
  8. };
  9. }

1.2 进阶版:支持立即执行

实际应用中常需要首次触发立即执行,后续触发才防抖。通过添加immediate参数实现:

  1. function debounce(func, delay, immediate = false) {
  2. let timer = null;
  3. return function(...args) {
  4. const context = this;
  5. if (timer) clearTimeout(timer);
  6. if (immediate && !timer) {
  7. func.apply(context, args);
  8. }
  9. timer = setTimeout(() => {
  10. if (!immediate) {
  11. func.apply(context, args);
  12. }
  13. timer = null;
  14. }, delay);
  15. };
  16. }
  17. // 使用示例
  18. const inputHandler = debounce(() => {
  19. console.log('Input processed');
  20. }, 300, true);

1.3 性能优化要点

  • 内存管理:确保在组件卸载时清除定时器(React中可在useEffect的cleanup函数中执行)
  • 参数传递:使用剩余参数(…)和apply确保参数完整传递
  • this绑定:通过保存context解决函数内this指向问题

二、深拷贝算法的递归实现

深拷贝是处理复杂数据结构的必备技能,Day2要求实现一个能处理循环引用的深拷贝函数。

2.1 基础递归实现

  1. function deepClone(obj, hash = new WeakMap()) {
  2. // 处理基本类型和null/undefined
  3. if (obj === null || typeof obj !== 'object') {
  4. return obj;
  5. }
  6. // 处理循环引用
  7. if (hash.has(obj)) {
  8. return hash.get(obj);
  9. }
  10. // 处理Date和RegExp
  11. if (obj instanceof Date) return new Date(obj);
  12. if (obj instanceof RegExp) return new RegExp(obj);
  13. // 创建对应类型的空对象
  14. const cloneObj = Array.isArray(obj) ? [] : {};
  15. hash.set(obj, cloneObj);
  16. // 递归拷贝属性
  17. for (let key in obj) {
  18. if (obj.hasOwnProperty(key)) {
  19. cloneObj[key] = deepClone(obj[key], hash);
  20. }
  21. }
  22. // 处理Symbol属性
  23. const symbolKeys = Object.getOwnPropertySymbols(obj);
  24. for (let symKey of symbolKeys) {
  25. cloneObj[symKey] = deepClone(obj[symKey], hash);
  26. }
  27. return cloneObj;
  28. }

2.2 关键技术点解析

  • WeakMap应用:使用WeakMap存储已拷贝对象,解决循环引用问题
  • 类型判断:通过instanceoftypeof准确识别Date、RegExp等特殊对象
  • Symbol属性处理:使用Object.getOwnPropertySymbols获取Symbol键名
  • 性能考虑:WeakMap的弱引用特性避免内存泄漏

2.3 测试用例设计

  1. // 测试循环引用
  2. const obj = { a: 1 };
  3. obj.self = obj;
  4. const cloned = deepClone(obj);
  5. console.log(cloned.self === cloned); // true
  6. // 测试特殊对象
  7. const date = new Date();
  8. const clonedDate = deepClone(date);
  9. console.log(clonedDate instanceof Date); // true

三、虚拟DOM树生成与Diff算法基础

Day2的高级挑战要求实现一个简化版虚拟DOM生成器,这是理解现代框架(如React、Vue)的核心基础。

3.1 虚拟DOM节点结构

  1. function createElement(type, props, ...children) {
  2. return {
  3. type,
  4. props: {
  5. ...props,
  6. children: children.map(child =>
  7. typeof child === 'object' ? child : createTextVNode(child)
  8. )
  9. }
  10. };
  11. }
  12. function createTextVNode(text) {
  13. return {
  14. type: 'TEXT',
  15. props: {
  16. nodeValue: text,
  17. children: []
  18. }
  19. };
  20. }

3.2 渲染函数实现

  1. function render(vnode, container) {
  2. // 处理文本节点
  3. if (vnode.type === 'TEXT') {
  4. return container.appendChild(
  5. document.createTextNode(vnode.props.nodeValue)
  6. );
  7. }
  8. // 创建DOM元素
  9. const dom = document.createElement(vnode.type);
  10. // 设置属性
  11. Object.keys(vnode.props)
  12. .filter(key => key !== 'children')
  13. .forEach(name => {
  14. dom[name] = vnode.props[name];
  15. });
  16. // 递归渲染子节点
  17. vnode.props.children.forEach(child =>
  18. render(child, dom)
  19. );
  20. container.appendChild(dom);
  21. }

3.3 基础Diff算法实现

  1. function diff(oldVNode, newVNode) {
  2. const patches = {};
  3. walk(oldVNode, newVNode, patches, 0);
  4. return patches;
  5. }
  6. function walk(oldVNode, newVNode, patches, index) {
  7. const currentPatch = [];
  8. // 节点替换
  9. if (!newVNode) {
  10. currentPatch.push({ type: 'REMOVE' });
  11. }
  12. // 文本节点更新
  13. else if (isText(oldVNode) && isText(newVNode)) {
  14. if (oldVNode.props.nodeValue !== newVNode.props.nodeValue) {
  15. currentPatch.push({ type: 'TEXT', content: newVNode.props.nodeValue });
  16. }
  17. }
  18. // 元素节点更新
  19. else if (oldVNode.type === newVNode.type) {
  20. // 属性更新
  21. const attrPatches = diffProps(oldVNode.props, newVNode.props);
  22. if (attrPatches.length) {
  23. currentPatch.push({ type: 'ATTR', attrs: attrPatches });
  24. }
  25. // 递归比较子节点
  26. diffChildren(
  27. oldVNode.props.children,
  28. newVNode.props.children,
  29. patches,
  30. index
  31. );
  32. }
  33. // 节点类型不同,完全替换
  34. else {
  35. currentPatch.push({ type: 'REPLACE', node: newVNode });
  36. }
  37. if (currentPatch.length) {
  38. patches[index] = currentPatch;
  39. }
  40. }

四、实战建议与进阶方向

  1. 测试驱动开发:为每个手写函数编写单元测试(使用Jest等框架)
  2. 性能基准测试:使用console.time对比不同实现的执行时间
  3. TypeScript改造:为所有函数添加类型定义,提升代码健壮性
  4. 浏览器兼容性:特别注意Symbol、WeakMap等新特性的兼容方案

五、常见问题解决方案

  1. 防抖函数中的this丢失:始终通过apply(this, args)保持上下文
  2. 深拷贝中的函数处理:明确需求决定是否拷贝函数(通常函数不拷贝)
  3. 虚拟DOM的key属性:为列表项添加唯一key提升Diff效率

通过Day2的这三个核心挑战,开发者不仅能巩固JavaScript基础,更能深入理解前端框架的底层原理。建议每天固定时间练习,逐步建立自己的前端工具库。

相关文章推荐

发表评论