【笔试篇】前端面试,看这一个系列就够了!(四)

485 阅读17分钟

前言

本系列文章根据作者多年一线大厂面试经验精选了一些高频面试题,目的是让有一定工作经验的读者用较短的时间消化复习完本文后,能够应对绝大多数大厂前端面试场景。

笔试部分

JS手写题尤其是实现一些新兴API的手写题如实现Promise等,这类题一定程度上考察了对API的理解程度以及是否用心准备了面试,所以一些常见的JS手写题在面试前还是要多复习复习,即使当场写不出来,也要知道思路,完全写不出来和知道思路写出大概的框架和全文背诵是完全不一样的,即使我们真的做不到BugFree,也至少不要交白卷。以下列举一些面试中常见的高频前端相关的手写题:

高频题

手写Promise,Promise.all,Promise.race
class MyPromise {
  constructor(executor) {
    this.status = 'pending';
    this.value = null;
    this.reason = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach(callback => callback());
      }
    };

    const reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(callback => callback());
      }
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
    onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason; };

    return new MyPromise((resolve, reject) => {
      const fulfilledCallback = () => {
        try {
          const result = onFulfilled(this.value);
          if (result instanceof MyPromise) {
            result.then(resolve, reject);
          } else {
            resolve(result);
          }
        } catch (error) {
          reject(error);
        }
      };

      const rejectedCallback = () => {
        try {
          const result = onRejected(this.reason);
          if (result instanceof MyPromise) {
            result.then(resolve, reject);
          } else {
            resolve(result);
          }
        } catch (error) {
          reject(error);
        }
      };

      if (this.status === 'fulfilled') {
        fulfilledCallback();
      } else if (this.status === 'rejected') {
        rejectedCallback();
      } else {
        this.onFulfilledCallbacks.push(fulfilledCallback);
        this.onRejectedCallbacks.push(rejectedCallback);
      }
    });
  }

  // 静态方法
  static resolve(value) {
    if (value instanceof MyPromise) {
      return value;
    }
    if (value && typeof value === 'object' && typeof value.then === 'function') {
      return new MyPromise((resolve, reject) => {
        value.then(resolve, reject);
      });
    }
    return new MyPromise(resolve => resolve(value));
  }

  static all(promiseList = []) {
    if (!Array.isArray(promiseList)) {
      throw new TypeError('参数必须是一个数组');
    }

    if (promiseList.length === 0) {
      return new MyPromise(resolve => resolve([]));
    }

    let count = 0;
    let result = Array(promiseList.length).fill(null);

    return new MyPromise((resolve, reject) => {
      promiseList.forEach((item, index) => {
        MyPromise.resolve(item).then(value => {
          result[index] = value;
          count++;
          if (count === promiseList.length) {
            resolve(result);
          }
        }, reject);
      });
    });
  }

  static race(promiseList = []) {
    if (!Array.isArray(promiseList)) {
      throw new TypeError('参数必须是一个数组');
    }

    return new MyPromise((resolve, reject) => {
      promiseList.forEach(item => {
        MyPromise.resolve(item).then(resolve, reject);
      });
    });
  }
}
实现Promise Scheduler,支持传入最大并发数量和order
    class Scheduler {
      constructor(max) {
        this.max = max;  // 最大并发数
        this.count = 0;   // 当前并发数
        this.queue = [];  // 等待队列
      }
      async add(promiseCreator) {
        // 如果当前并发数已经达到最大并发数,则将当前任务添加到队列中等待
        if (this.count >= this.max) {
          await new Promise(resolve => this.queue.push(resolve));
        }

        // 开始执行任务,并发数增加
        this.count++;

        try {
          // 执行promiseCreator()返回的任务
          const res = await promiseCreator();
          return res;
        } finally {
          // 任务执行完毕后并发数减少
          this.count--;

          // 如果队列中有等待的任务,唤醒下一个等待任务
          if (this.queue.length > 0) {
            // 依次唤醒任务
            this.queue.shift()(); // resolve 阻塞的任务
          }
        }
      }
    }
    // 非async/await版本
    class Scheduler {
      constructor(max) {
        this.max = max;
        this.count = 0;
        this.queue = [];
      }

      add(promiseScheduler) {
        return new Promise((resolve, reject) => {
          const runTask = () => {
            this.count++;
            promiseScheduler()
              .then(resolve) // 任务完成,调用 `resolve`
              .catch(reject) // 处理错误
              .finally(() => {
                this.count--;
                if (this.queue.length > 0) {
                  this.queue.shift()(); // 取出队列中的下一个任务执行
                }
              });
          };

          if (this.count >= this.max) {
            this.queue.push(runTask); // 超过最大并发数,加入队列
          } else {
            runTask(); // 直接执行任务
          }
        });
      }
    }
    const timeout = time =>
      new Promise(resolve => {
        setTimeout(resolve, time);
      });
    const scheduler = new Scheduler(2);
    const addTask = (time, order) => {
      //add返回一个promise,参数也是一个promise
      scheduler.add(() => timeout(time)).then(() => console.log(order));
    };
    addTask(1000, '1');
    addTask(500, '2');
    addTask(300, '3');
    addTask(400, '4');
发布订阅模式实现event bus
    class EventEmitter {
      constructor() {
        this.events = {};
      }
      on(eventName, callback, params) {
        if (!this.events[eventName]) {
          this.events[eventName] = [];
        }
        const exists = this.events[eventName].some(item => item.callback === callback);
        if (!exists) {
          this.events[eventName].push({ callback, params });
        }
      }
      off(eventName, callback) {
        if (Array.isArray(this.events[eventName])) {
          if (!callback) {
            delete this.events[eventName];
            return;
          }
          this.events[eventName] = this.events[eventName].filter((item) => {
            return callback !== item.callback;
          });
        }
      }
      once(eventName, callback, params) {
        const wrapper = (extendParams) => {
          callback({
            ...(params || {}),
            ...(extendParams || {}),
          });
          this.off(eventName, wrapper);
        };
        wrapper._isOnce = true;
        this.on(eventName, wrapper);
      }
      emit(eventName, params) {
        (this.events[eventName] || []).forEach((item) => {
          if (typeof item.callback === 'function') {
            item.callback({
              ...(item.params || {}),
              ...(params || {})
            });
          }
        });
      }
    }
    
实现async/await函数
function asyncFunction(generatorFunc) {
  return function(...args) {
    const generator = generatorFunc(...args);

    function handle(result) {
      if (result.done) return Promise.resolve(result.value);

      return Promise.resolve(result.value).then(
        res => handle(generator.next(res)),
        err => handle(generator.throw(err))
      );
    }

    try {
      return handle(generator.next());
    } catch (ex) {
      return Promise.reject(ex);
    }
  };
}

// 示例异步函数
function delay(value, ms) {
  return new Promise(resolve => setTimeout(() => resolve(value), ms));
}

// 使用我们的 asyncFunction 替代 async 语法
const asyncTask = asyncFunction(function* () {
  try {
    const a = yield delay(1, 500); // 模仿 await
    console.log(a);

    const b = yield delay(2, 500);
    console.log(b);

    return a + b;
  } catch (error) {
    console.error(error);
  }
});

asyncTask().then(result => {
  console.log('Result:', result);
}).catch(err => {
  console.error('Error:', err);
});
扁平数据结构转树型结构,递归版本与Map版本/反过来树型结果转扁平结果呢?
const flatData = [
  { id: 1, name: '根节点', parentId: null },
  { id: 2, name: '节点 1-1', parentId: 1 },
  { id: 3, name: '节点 1-2', parentId: 1 },
  { id: 4, name: '节点 2-1', parentId: 2 },
  { id: 5, name: '节点 2-2', parentId: 2 },
  { id: 6, name: '节点 3-1', parentId: 3 },
  { id: 7, name: '节点 3-2', parentId: 3 },
  { id: 8, name: '孤立节点', parentId: null },
];
function flat2Tree(flat) {
  if (!Array.isArray(flat)) {
    return [];
  }
  const map = new Map();
  const tree = [];
  flat.forEach((item) => {
    map.set(item.id, {
      ...item,
      children: []
    });
  });
  flat.forEach((item) => {
    if (item.parentId === null) {
      tree.push(map.get(item.id));
    } else {
      // 否则是一个子节点
      const parent = map.get(item.parentId);
      if (parent) {
        parent.children.push(map.get(item.id));
      }
    }
  });
  return tree;
}
function tree2Flat(tree) {
  if (!Array.isArray(tree)) {
    return [];
  }
  const flat = [];
  const _tree2Flat = (tree) => {
    tree.forEach((item) => {
      flat.push({
        id: item.id,
        name: item.name,
        parentId: item.parentId
      });
      if (Array.isArray(item.children) && item.children.length > 0) {
        _tree2Flat(item.children);
      }
    });
  };
  _tree2Flat(tree);
  return flat;
}
console.log(flat2Tree(flatData));
console.log(tree2Flat(flat2Tree(flatData)));
实现类似lodash.get的效果
    function get(obj, path) {
      if (!obj || !path) {
        return obj;
      }
      const pathArray = path.replace(/\[(\d+)\]/g, '.$1').split(".");
      return pathArray.reduce((acc, currentPath) => {
          if (acc[currentPath] === undefined) {
              return undefined;
          }
          return acc[currentPath];
      }, obj);
    }
防抖与节流
    // 基础版
    function debounce(callback, delay) {
      let timer = null;
      return function (...args) {
        if (timer) {
          clearTimeout(timer);
        }
        timer = setTimeout(() => {
          callback.apply(this, args);
        }, delay);
      };
    }
    function throttle(callback, delay) {
      let timer = null;
      return function (...args) {
        if (timer) return;
        timer = setTimeout(() => {
          callback.apply(this, args); // 保留上下文和参数
          timer = null; // 允许下一次调用
        }, delay);
      };
    }
    // 高级版
    function throttle(fn, delay, { leading = true, trailing = true } = {}) {
        let timer = null;
        let lastArgs, lastThis, lastResult;
        let lastTime = 0;

        function invoke(time) {
                lastTime = time;
                lastResult = fn.apply(lastThis, lastArgs);
                lastArgs = lastThis = null;
        }

        function throttled(...args) {
                const now = Date.now();
                lastArgs = args;
                lastThis = this;

                if (!lastTime && !leading) lastTime = now;

                const remain = delay - (now - lastTime);

                if (remain <= 0) {
                        if (timer) clearTimeout(timer);
                        timer = null;
                        invoke(now);
                } else if (trailing && !timer) {
                        timer = setTimeout(() => {
                                timer = null;
                                if (trailing && lastArgs) {
                                        invoke(Date.now());
                                }
                        }, remain);
                }

                return lastResult;
        }

        throttled.cancel = () => {
                if (timer) clearTimeout(timer);
                timer = null;
                lastTime = 0;
                lastArgs = lastThis = null;
        };

        throttled.flush = () => {
                if (timer) {
                        clearTimeout(timer);
                        timer = null;
                        if (trailing && lastArgs) {
                                invoke(Date.now());
                        }
                }
                return lastResult;
        };

        return throttled;
    }

    function debounce(fn, delay, { leading = false, trailing = true } = {}) {
        let timer = null;
        let lastArgs, lastThis, lastResult;
        let called = false;

        function invoke() {
                lastResult = fn.apply(lastThis, lastArgs);
                called = true;
                lastArgs = lastThis = null;
        }

        function debounced(...args) {
                lastArgs = args;
                lastThis = this;

                if (timer) clearTimeout(timer);

                // 首次立即执行
                if (leading && !called) {
                        invoke();
                }

                timer = setTimeout(() => {
                        timer = null;
                        // 尾部执行
                        if (trailing && (!leading || called)) {
                                invoke();
                        }
                        called = false; // 新周期允许再次 leading
                }, delay);

                return lastResult;
        }

        debounced.cancel = () => {
                if (timer) clearTimeout(timer);
                timer = null;
                called = false;
                lastArgs = lastThis = null;
        };

        debounced.flush = () => {
                if (timer) {
                        clearTimeout(timer);
                        timer = null;
                        if (trailing && lastArgs) {
                                invoke();
                        }
                        called = false;
                }
                return lastResult;
        };

        return debounced;
    }
深拷贝,如何解决循环引用的问题?
    function deepClone(obj, hash = new WeakMap()) {
        if (obj === null || typeof obj !== 'object') {
            return obj;
        }

        if (hash.has(obj)) {
            return hash.get(obj);
        }

        let copy = Array.isArray(obj) ? [] : {};
        hash.set(obj, copy);

        Object.keys(obj).forEach(key => {
            copy[key] = deepClone(obj[key], hash);
        });

        return copy;
    }
    
手动实现Object.assign方法,进行对象的浅拷贝
function objectAssign(target, ...sources) {
  if (target === null || target === undefined) {
    throw new TypeError('Cannot convert undefined or null to object');
  }

  sources.forEach((source) => {
    if (source !== null && typeof source === 'object') {
      Object.keys(source).forEach((key) => {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      });
    }
  });
  return target;
}
手动实现一个深度合并对象的功能,考虑数组、对象嵌套等复杂情况
function deepMerge(target, ...sources) {
  if (target === null || target === undefined) {
    throw new TypeError('Cannot convert undefined or null to object');
  }

  // 处理每个源对象
  sources.forEach((source) => {
    if (source !== null && typeof source === 'object') {
      // 遍历源对象的所有键
      Object.keys(source).forEach((key) => {
        const targetValue = target[key];
        const sourceValue = source[key];

        // 如果目标值和源值都是对象,进行递归合并
        if (targetValue !== undefined && targetValue !== null && typeof targetValue === 'object' &&
            sourceValue !== null && typeof sourceValue === 'object') {
          // 如果是对象,则递归调用 deepMerge
          target[key] = deepMerge(targetValue, sourceValue);
        }
        // 如果目标值和源值都是数组,合并数组
        else if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
          target[key] = [...targetValue, ...sourceValue];  // 合并数组,可以根据需求修改为去重等
        }
        // 其他情况(包括原始类型),直接覆盖
        else {
          target[key] = sourceValue;
        }
      });
    }
  });

  return target;
}
实现call apply bind
    Function.prototype.myCall = function (context, ...args) {
      if (typeof this !== 'function') {
        throw new TypeError("Not callable"); // 只有函数才能调用 call
      }
      context = context || globalThis;
      const fn = Symbol('fn');
      context[fn] = this;
      const result = context[fn](...args);
      delete context[fn];
      return result;
    }
    Function.prototype.myApply = function (context, args = []) {
      if (typeof this !== 'function') {
        throw new TypeError("Not callable"); // 只有函数才能调用 call
      }
      context = context || globalThis;
      const fn = Symbol('fn');
      context[fn] = this;
      const result = context[fn](...(args || []));
      delete context[fn];
      return result;
    }
    Function.prototype.myBind = function (context, ...args) {
      if (typeof this !== 'function') {
        throw new TypeError('Not callable');
      }
      const fn = this;
      // 返回一个新函数
      function boundFunction(...newArgs) {
        // 如果通过 new 调用,忽略绑定的 context,使用新创建的 this
        const isCalledWithNew = this instanceof boundFunction;
        return fn.apply(isCalledWithNew ? this : context, [...args, ...newArgs]);
      }
      // 设置原型链,确保 boundFunction 是 fn 的实例
      boundFunction.prototype = Object.create(fn.prototype);
      return boundFunction;
    };
解析URL Params为对象
    function parseQueryString(url) {
      const params = new URL(url).searchParams;
      const result = {};
      for (const [key, value] of params.entries()) {
        result[key] = value;
      }
      return result;
    }
    function parseQueryString2(url) {
      const queryString = url.split('?')[1];
      if (!queryString) {
        return {};
      }
      return queryString.split('&').reduce((acc, current) => {
        const [key, value] = current.split('=');
        const decodedKey = decodeURIComponent(key);
        const decodedValue = decodeURIComponent(value || ''); // 处理值可能为空的情况
        if (acc[decodedKey]) {
          // 如果键已经存在,合并为数组
          acc[decodedKey] = Array.isArray(acc[decodedKey]) ? [...acc[decodedKey], decodedValue] : [acc[decodedKey], decodedValue];
        } else {
          acc[decodedKey] = decodedValue;
        }
        return acc;
      }, {});
    }
数组去重
    function uniqueArray(array) {
      return [...new Set(array)];
    }
    function uniqueArray2(array) {
      const map = new Map();
      return array.filter((item) => {
        if (!map.has(item)) {
          map.set(item, true);
          return true
        }
        return false;
      });
    }
    function uniqueArray(array) {
      return array.reduce((acc, current) => {
        if (!acc.includes(current)) acc.push(current);
        return acc;
      }, []);
    }
数组/对象扁平化,支持传入深度
    function flatArray(array = [], depth = Infinity) {
      if (!Array.isArray(array)) {
        throw new TypeError('Input must be an array'); // 添加类型检查
      }
      const result = [];
      const _flat = (arr, dep) => {
        arr.forEach((item) => {
          if (Array.isArray(item) && dep > 0) {
            _flat(item, dep - 1); // 递归展开子数组
          } else {
            result.push(item); // 非数组或达到深度限制,直接添加
          }
        });
      };
      _flat(array, depth);
      return result;
    }
    
    function flattenObject(obj, prefix = '') {
      const result = {};

      if (!obj || typeof obj !== 'object') {
        return result; // 处理 `null` 或者 `非对象` 输入的情况
      }

      Object.keys(obj).forEach((key) => {
        const value = obj[key];
        const newKey = prefix ? `${prefix}.${key}` : key; // 避免开头多余的点

        if (value && typeof value === 'object' && !Array.isArray(value)) {
          Object.assign(result, flattenObject(value, newKey));
        } else {
          result[newKey] = value;
        }
      });

      return result;
    }
setTimeout模拟setTimeInterval(为什么要模拟,解决了什么问题?)
    // setInterval执行是固定的时间周期,如果回调函数执行时间长于interval时间,会在回调执行完成后立刻执行下一次回调,造成事件堆积,而setTimeout则是timeout + 函数回调执行事件,这个事件不固定,实际间隔事件是上一次回调执行事件 + timeout事件,虽然间隔事件不固定但是不会造成事件堆积,比较稳定
    function customInterval(callback, interval, ...args) {
      let timerId;
      const wrapper = () => {
        callback(...args);
        timerId = setTimeout(wrapper, interval);
      };
      // 初始化第一次调用
      setTimeout(wrapper, interval);
      return () => {
        return clearTimeout(timerId);
      };
    }
模拟sleep
    function sleep(time) {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve();
        }, time);
      });
    }
    function sleep2(time) {
      let currentTime = +new Date();
      while (currentTime + time > + new Date()) {
        // nothing to do
      }
    }
冒泡排序
    function bubbleSort(arr = []) {
      if (!Array.isArray(arr)) {
        return;
      }
      let swapped;
      for (let i = 0; i < arr.length; i++) {
        swapped = false;
        for (let j = 0; j < arr.length - i - 1; j++) {
          if (arr[j] > arr[j + 1]) {
            [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
            swapped = true; // 记录此次循环有交换动作
          }
        }
        // 如果内层循环没有任何交换,直接结束
        if (!swapped) {
          break;
        }
      }
      return arr;
    }
快速排序
// 时间复杂度 O(n log n)
function quickSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }

  const pivotIndex = Math.floor(arr.length / 2);
  const pivot = arr[pivotIndex];
  const left = [];
  const right = [];

  for (let i = 0; i < arr.length; i++) {
    if (i === pivotIndex) continue;
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }

  return [...quickSort(left), pivot, ...quickSort(right)];
}
二分查找
    function binarySearch(arr = [], target) {
      // 检查输入合法性
      if (typeof target === 'undefined' || !Array.isArray(arr) || arr.length === 0) {
        return -1; // 返回 -1 表示未找到
      }
      let left = 0;
      let right = arr.length - 1;
      while (left <= right) {
        let mid = Math.floor((left + right) / 2);
        if (arr[mid] === target) {
          return mid;
        } else if (arr[mid] > target) {
          right = mid - 1;
        } else {
          left = mid + 1;
        }
      }
      return -1;
    }
函数柯里化
function curry(func) {
  return function curried(...args) {
    if (func.length === args.length) {
      return func.apply(this, args);
    } else {
      return (...newArgs) => {
        return curried.call(this, ...args, ...newArgs);
      }
    }
  }
}
// 示例
function add(a, b, c) {
  return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
解析类似{{name}}的模板,替换为实际数据
function renderTemplate(template, data) {
  return template.replace(/\{\{(.*?)\}\}/g, (match, key) => {
    return data[key] !== undefined ? data[key] : match; // 如果 `data[key]` 不存在,则保持原样
  });
}
// 示例数据
const template = "Hello, {{name}}! You have {{count}} new messages.";
const data = {
name: "Alice",
count: 5
};

const result = renderTemplate(template, data);
console.log(result); // 输出: Hello, Alice! You have 5 new messages.
手动模拟数据双向绑定
// Vue2
function defineReactive(obj, key, callback) {
  let val = obj[key];
  Object.defineProperty(obj, key, {
    get() {
      return val;
    },
    set(newVal) {
      if (val !== newVal) {
        val = newVal;
        callback(newVal);
      }
    }
  });
}
const data = {};
defineReactive(data, 'name', (newValue) => {
  console.log(`视图更新: ${newValue}`);
});
data.name = 'Alice'; // 输出: 视图更新: Alice

// Vue3
function reactive(obj, callback) {
  if (typeof obj !== 'object' || obj === null) {
    return obj; // 仅对对象和数组进行处理
  }

  return new Proxy(obj, {
    get(target, key, receiver) {
      const value = Reflect.get(target, key, receiver);
      console.log(`读取属性 ${key}:`, value); // 可选:调试信息
      return typeof value === 'object' && value !== null
        ? reactive(value, callback) // 递归处理嵌套对象
        : value;
    },
    set(target, key, value, receiver) {
      const oldValue = target[key];
      if (oldValue !== value) {
        const result = Reflect.set(target, key, value, receiver);
        console.log(`设置属性 ${key}:`, value); // 可选:调试信息
        callback(key, value); // 触发回调
        return result;
      }
      return true;
    },
    deleteProperty(target, key) {
      if (key in target) {
        const result = Reflect.deleteProperty(target, key);
        console.log(`删除属性 ${key}`); // 可选:调试信息
        callback(key, undefined); // 触发回调
        return result;
      }
      return false;
    }
  });
}

// 测试代码
const data = reactive(
  { user: { name: 'Alice', age: 25 }, items: [1, 2, 3] },
  (key, value) => {
    console.log(`视图更新: ${key} -> ${value}`);
  }
);

data.user.name = 'Bob'; // 输出: 设置属性 name: Bob; 视图更新: name -> Bob
data.user.age = 26; // 输出: 设置属性 age: 26; 视图更新: age -> 26
data.user.gender = 'female'; // 输出: 设置属性 gender: female; 视图更新: gender -> female
delete data.user.name; // 输出: 删除属性 name; 视图更新: name -> undefined
data.items.push(4); // 输出: 设置属性 3: 4; 视图更新: 3 -> 4
实现一个简单的事件委托
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <ul id="list">
    <li class="item">Item 1</li>
    <li class="item">Item 2</li>
    <li class="item">Item 3</li>
  </ul>
  <script>
    function delegate(parentElement, eventType, selector, callback) {
      parentElement.addEventListener(eventType, function (event) {
        const targetElement = event.target;
        // 判断目标元素是否匹配选择器
        if (targetElement.matches(selector)) {
          callback.call(targetElement, event);
        }
      });
    }
    const list = document.getElementById('list');
    // 使用事件代理
    delegate(list, 'click', '.item', function (event) {
      console.log('Clicked item:', this.textContent);
    });
  </script>
</body>
</html>
Object.create()来实现对象继承的简易版
// 定义父对象
const parent = {
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  },
};

// 创建一个构造函数
function createChild(name) {
  const child = Object.create(parent); // 继承父对象
  child.name = name; // 添加自己的属性
  return child;
}

// 创建实例
const child1 = createChild("Alice");
const child2 = createChild("Bob");

// 测试
child1.greet(); // 输出: "Hello, my name is Alice"
child2.greet(); // 输出: "Hello, my name is Bob"
模拟new操作符
function myNew(Constructor, ...args) {
  // 1. 创建一个空对象,并将其原型设置为 Constructor.prototype
  const obj = Object.create(Constructor.prototype);
  // 2. 调用构造函数,将 `this` 指向新对象
  const result = Constructor.apply(obj, args);
  // 3. 如果构造函数返回的是一个对象类型的值,则返回该值,否则返回新对象
  return typeof result === "object" && result !== null ? result : obj;
}
// 定义一个构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = function () {
    console.log(`Hello, my name is ${this.name}, and I am ${this.age} years old.`);
  };
}
// 使用原生 new
const person1 = new Person("Alice", 25);
person1.sayHello(); // 输出: Hello, my name is Alice, and I am 25 years old.

// 使用模拟的 myNew
const person2 = myNew(Person, "Bob", 30);
person2.sayHello(); // 输出: Hello, my name is Bob, and I am 30 years old.
实现支持链式调用的对象方法,通常与this绑定相关
class Chainable {
  constructor(value = 0) {
    this.value = value;
  }

  add(num) {
    this.value += num;
    return this; // 返回当前实例
  }

  subtract(num) {
    this.value -= num;
    return this; // 返回当前实例
  }

  multiply(num) {
    this.value *= num;
    return this; // 返回当前实例
  }

  print() {
    console.log(this.value);
    return this; // 返回当前实例
  }
}

// 测试链式调用
const instance = new Chainable();
instance.add(10).subtract(4).multiply(2).print(); // 输出: 12
模拟Object.create
function objectCreate(proto) {
  if (typeof proto !== 'object' && typeof proto !== 'function' || proto === null) {
    throw new TypeError('Object prototype may only be an Object or null');
  }

  // 创建一个空对象并设置其原型
  function F() { }
  F.prototype = proto;
  return new F();
}

// 测试
const proto = { greet: 'hello' };
const obj = objectCreate(proto);
console.log(obj.greet); // 输出: hello
console.log(Object.getPrototypeOf(obj) === proto); // 输出: true
实现isEqual
function isEqual(a, b, map = new WeakMap()) {
  // 1️⃣ 处理基本类型
  if (a === b) return true;
  if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
    return false;
  }

  // 2️⃣ 处理循环引用
  if (map.has(a) && map.get(a) === b) return true;
  map.set(a, b);

  // 3️⃣ 获取键值
  const keysA = Object.keys(a);
  const keysB = Object.keys(b);
  if (keysA.length !== keysB.length) return false;

  // 4️⃣ 递归对比
  return keysA.every(key => isEqual(a[key], b[key], map));
}

// 🛠️ 测试用例
const obj1 = { a: 1 };
obj1.self = obj1; // 创建循环引用

const obj2 = { a: 1 };
obj2.self = obj2; // 创建相同的循环引用

console.log(isEqual(obj1, obj2)); // true(不会无限递归)

const obj3 = { a: 1, b: { c: 2 } };
const obj4 = { a: 1, b: { c: 2 } };

console.log(isEqual(obj3, obj4)); // true(深度相等)
console.log(isEqual({ a: 1 }, { a: 2 })); // false
console.log(isEqual([1, [2, 3]], [1, [2, 3]])); // true
反转链表
翻转二叉树
LRU缓存
实现千位分隔符
大数相加
字符串相乘
字符串翻转
比较版本号
最长回文子串
排序链表
前K个高频元素
K 个一组翻转链表

参考题

自定义React Hooks实现useDebounce并使用它
// 📂 src/hooks/useDebounce.js
import { useRef, useCallback } from 'react';
function useDebouncedCallback(callback, delay) {
    const timer = useRef();
    const debouncedFn = useCallback((...args) => {
    if (timer.current) {
        clearTimeout(timer.current);
    }
    timer.current = setTimeout(() => {
        callback(...args);
    }, delay)}, [callback, delay]);
    return debouncedFn;
 }

import { useState, useEffect } from "react";
function useDebounce(value, delay = 500) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler); // 组件卸载或 value 变化时清除定时器
    };
  }, [value, delay]);

  return debouncedValue;
}

export default useDebounce;

// 📂 src/components/DebouncedInput.js
import React, { useState } from "react";
import useDebounce from "../hooks/useDebounce";

function DebouncedInput() {
  const [text, setText] = useState("");
  const debouncedText = useDebounce(text, 500); // 使用 500ms 防抖

  return (
    <div>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Type something..."
      />
      <p>Debounced Value: {debouncedText}</p>
    </div>
  );
}

export default DebouncedInput;
基于Hash和History API实现一个简易的前端路由机制
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>简易前端路由HASH</title>
</head>
<body>
  <div id="app"></div>

  <nav>
    <a href="#/home">Home</a>
    <a href="#/about">About</a>
    <a href="#/contact">Contact</a>
  </nav>

  <script>
    // 模拟页面组件
    const routes = {
      '/home': '<h1>Home Page</h1>',
      '/about': '<h1>About Page</h1>',
      '/contact': '<h1>Contact Page</h1>',
    };

    function renderPage() {
      const hash = window.location.hash || '#/home'; // 默认是首页
      const page = hash.slice(1); // 去掉#符号
      document.getElementById('app').innerHTML = routes[page] || '<h1>404 Not Found</h1>';
    }

    // 初次渲染
    window.addEventListener('load', renderPage);

    // 监听 Hash 变化
    window.addEventListener('hashchange', renderPage);
  </script>
</body>
</html>

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>简易前端路由History</title>
</head>

<body>
  <div id="app"></div>

  <nav>
    <a href="javascript:void(0);" onclick="navigate('/home')">Home</a>
    <a href="javascript:void(0);" onclick="navigate('/about')">About</a>
    <a href="javascript:void(0);" onclick="navigate('/contact')">Contact</a>
  </nav>

  <script>
    // 模拟页面组件
    const routes = {
      '/home': '<h1>Home Page</h1>',
      '/about': '<h1>About Page</h1>',
      '/contact': '<h1>Contact Page</h1>',
    };

    // 页面渲染函数
    function renderPage(path) {
      document.getElementById('app').innerHTML = routes[path] || '<h1>404 Not Found</h1>';
    }

    // 路由导航函数
    function navigate(path) {
      history.pushState({}, '', path); // 改变URL
      renderPage(path); // 渲染对应的页面
    }

    // 监听历史记录的变化
    window.addEventListener('popstate', () => {
      renderPage(location.pathname); // 根据当前路径渲染页面
    });

    // 初次渲染,基于当前 URL
    window.addEventListener('load', () => {
      renderPage(location.pathname); // 默认渲染当前页面
    });
  </script>
</body>

</html>
模块化开发:实现requireexports:理解模块化编程,手写实现简易模块系统
// 简易的模块缓存
const moduleCache = {};

// 模拟 require 实现
function require(moduleName) {
  // 如果模块已经缓存,则直接返回缓存内容
  if (moduleCache[moduleName]) {
    return moduleCache[moduleName];
  }

  // 创建一个新的模块对象
  const module = {
    exports: {} // 模块导出的内容
  };

  // 模拟 Node.js 的模块查找过程,假设模块是一个普通的 JS 文件
  const script = modules[moduleName];

  // 立即执行模块代码,将模块的 exports 赋值到 module.exports 上
  script(module.exports, require);

  // 将模块存入缓存
  moduleCache[moduleName] = module.exports;

  return module.exports;
}

// 模拟 exports 对象
function exports() {
  return {};
}

// 模块系统中的示例模块
const modules = {
  'math.js': function (exports, require) {
    // 模拟导出模块功能
    exports.add = function (a, b) {
      return a + b;
    };
    exports.subtract = function (a, b) {
      return a - b;
    };
  },

  'app.js': function (exports, require) {
    const math = require('math.js'); // 加载 math.js 模块
    exports.run = function () {
      console.log('Add 1 + 2 =', math.add(1, 2));
      console.log('Subtract 5 - 3 =', math.subtract(5, 3));
    };
  }
};

// 使用模块系统
const app = require('app.js');
app.run();
什么是HOC,写一个DEMO
// 需求
// 我们有一个组件 DataComponent,它用于展示数据。
// 通过 HOC,给 DataComponent 添加加载状态和计数器功能。
// 实现步骤
// 1. 创建 HOC withLoading:为组件添加加载状态
// withLoading.js
import React from 'react';
/**
 * HOC: withLoading
 * 为组件提供加载状态功能
 * @param {React.Component} WrappedComponent - 被增强的组件
 * @returns {React.Component} - 新的增强组件
 */
function withLoading(WrappedComponent) {
  // 返回一个新的组件,接受props作为参数
  return function EnhancedComponent(props) {
    // 假设传递的 props 中包含 isLoading 来控制加载状态
    if (props.isLoading) {
      return <div>Loading...</div>;
    }

    // 否则渲染传递给 HOC 的原始组件
    return <WrappedComponent {...props} />;
  };
}

export default withLoading;

// 2. 创建 HOC withCounter:为组件添加计数器功能
// withCounter.js
import React, { useState, useEffect } from 'react';
/**
 * HOC: withCounter
 * 为组件提供计数器功能
 * @param {React.Component} WrappedComponent - 被增强的组件
 * @returns {React.Component} - 新的增强组件
 */
function withCounter(WrappedComponent) {
  // 返回一个新的组件,接受props作为参数
  return function EnhancedComponent(props) {
    // 使用 useState 来实现计数器
    const [count, setCount] = useState(0);

    // 使用 useEffect 来模拟每秒钟更新一次计数器
    useEffect(() => {
      const intervalId = setInterval(() => {
        setCount(prevCount => prevCount + 1);
      }, 1000);

      // 清理定时器,防止内存泄漏
      return () => clearInterval(intervalId);
    }, []);

    // 将 count 作为 prop 传递给 WrappedComponent
    return <WrappedComponent {...props} count={count} />;
  };
}

export default withCounter;

// 3. 创建要增强的组件 DataComponent
// DataComponent.js
import React from 'react';

/**
 * 这是一个基础组件,用于展示数据和计数器
 */
function DataComponent({ data, count }) {
  return (
    <div>
      <h1>Data: {data}</h1>
      <p>Render count: {count}</p>
    </div>
  );
}

export default DataComponent;

// 4. 组合 HOC 和组件
// 在这里我们将 withLoading 和 withCounter 两个 HOC 组合使用来增强 DataComponent。
// App.js
import React, { useState } from 'react';
import DataComponent from './DataComponent';
import withLoading from './withLoading';
import withCounter from './withCounter';

// 使用 HOC 包装组件
const EnhancedDataComponent = withCounter(withLoading(DataComponent));

function App() {
  const [isLoading, setIsLoading] = useState(true);

  // 模拟数据加载过程
  setTimeout(() => {
    setIsLoading(false); // 2秒后停止加载状态
  }, 2000);

  return (
    <div>
      <h1>HOC Demo</h1>
      <EnhancedDataComponent isLoading={isLoading} data="Hello, World!" />
    </div>
  );
}

export default App;
// 详细注释
// withLoading HOC:
// 这个 HOC 接收一个组件 WrappedComponent,返回一个新的组件。
// 在新组件中,根据 isLoading prop 判断是否显示加载提示。如果 isLoading 为 true,则显示 Loading...,否则渲染原始组件。
// withCounter HOC:
// 这个 HOC 同样接收一个组件 WrappedComponent,返回一个新的组件。
// 在新组件中,使用 useState 来定义一个 count 变量,使用 useEffect 设置一个定时器,每秒钟增加 count 值。
// 通过 props 将 count 传递给原始组件。
// DataComponent:
// 这是原始的展示数据组件,它接收 data 和 count 作为 props 并显示它们。
// 组合 HOC:
// 在 App 组件中,EnhancedDataComponent 是通过将 withLoading 和 withCounter 组合而成的增强版组件。这个组件能够显示数据、加载状态和计数器。
// 模拟加载:
// 在 App 组件中,isLoading 状态通过 setTimeout 模拟 2 秒钟后停止加载状态,展示最终数据。
// 总结
// 通过这个 Demo,我们展示了如何通过 HOC 模式复用组件逻辑。
// withLoading 为组件提供加载状态功能。
// withCounter 为组件提供计数器功能。
// 通过组合这两个 HOC,我们得到了一个功能强大的 EnhancedDataComponent,它能够同时处理加载状态和计数器逻辑。
// HOC 本质上就是一个闭包模式,它通过返回新的组件并保持对外部作用域的引用(如状态、函数等)来增强功能。
// 这种设计模式在大型 React 应用中非常常见,能够帮助我们分离关心点,提升代码的复用性和可维护性。
写一个REACT模态框
import React from "react";
import "./Modal.css"; // 引入样式

const Modal = ({ isOpen, onClose, children }) => {
  if (!isOpen) return null; // 如果 isOpen 为 false,返回 null,表示不渲染模态框

  return (
    <div className="modal-overlay">
      <div className="modal">
        <button className="modal-close" onClick={onClose}>
          &times;
        </button>
        <div className="modal-content">{children}</div>
      </div>
    </div>
  );
};

export default Modal;

/* 模态框的背景 */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5); /* 半透明背景 */
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 999;
}

/* 模态框容器 */
.modal {
  background-color: #fff;
  padding: 20px;
  border-radius: 8px;
  max-width: 500px;
  width: 100%;
  position: relative;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}

/* 关闭按钮 */
.modal-close {
  position: absolute;
  top: 10px;
  right: 10px;
  background: transparent;
  border: none;
  font-size: 24px;
  cursor: pointer;
}

/* 模态框内容 */
.modal-content {
  padding: 20px;
  text-align: center;
}

import React, { useState } from "react";
import Modal from "./Modal";

const App = () => {
  const [isModalOpen, setIsModalOpen] = useState(false);

  const openModal = () => setIsModalOpen(true);
  const closeModal = () => setIsModalOpen(false);

  return (
    <div>
      <h1>React 模态框示例</h1>
      <button onClick={openModal}>打开模态框</button>

      <Modal isOpen={isModalOpen} onClose={closeModal}>
        <h2>欢迎来到模态框!</h2>
        <p>这里可以放置任意内容。</p>
        <button onClick={closeModal}>关闭</button>
      </Modal>
    </div>
  );
};

export default App;
用Promise封装AJAX请求
function fetchRequest(url, method = 'GET', data = null) {
  const options = {
    method,
    headers: {
      'Content-Type': 'application/json',
    },
  };

  // 如果有数据并且是 POST 请求,添加请求体
  if (data && method === 'POST') {
    options.body = JSON.stringify(data);
  }

  // 使用 fetch 发送请求
  return fetch(url, options)
    .then(response => {
      if (!response.ok) {
        // 如果响应不成功,抛出错误
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      return response.json(); // 解析 JSON 数据
    });
}

// 使用封装的 fetch 请求
fetchRequest('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => console.log(response))
  .catch(error => console.error(error));
实现instanceOf
function myInstanceOf(obj, constructor) {
  // 获取对象的原型链
  let proto = Object.getPrototypeOf(obj);

  // 逐级查找原型链
  while (proto) {
    if (proto === constructor.prototype) {
      return true; // 如果找到了构造函数的 prototype,返回 true
    }
    proto = Object.getPrototypeOf(proto); // 否则继续查找父级原型
  }

  return false; // 如果原型链最后没有找到,返回 false
}
选择排序
function selectionSort(arr) {
  let n = arr.length;

  // 外循环用于控制每一轮选择最小元素
  for (let i = 0; i < n - 1; i++) {
    let minIndex = i; // 假设当前元素是最小的

    // 内循环从未排序的部分中找到最小元素
    for (let j = i + 1; j < n; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j; // 更新最小元素的索引
      }
    }

    // 如果找到更小的元素,则交换
    if (minIndex !== i) {
      [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; // 交换
    }
  }

  return arr;
}

// 测试
const arr = [64, 25, 12, 22, 11];
console.log(selectionSort(arr));  // 输出:[11, 12, 22, 25, 64]
插入排序
function insertionSort(arr) {
  let n = arr.length;

  // 从第二个元素开始
  for (let i = 1; i < n; i++) {
    let current = arr[i];  // 当前要插入的元素
    let j = i - 1;

    // 找到合适的位置,将大于 current 的元素后移
    while (j >= 0 && arr[j] > current) {
      arr[j + 1] = arr[j]; // 将 arr[j] 向右移动一位
      j--;
    }

    // 插入 current 元素到合适的位置
    arr[j + 1] = current;
  }

  return arr;
}

// 测试
const arr = [64, 25, 12, 22, 11];
console.log(insertionSort(arr));  // 输出:[11, 12, 22, 25, 64]
归并排序
function mergeSort(arr) {
  // 基本情况:数组长度为1时已经有序
  if (arr.length <= 1) {
    return arr;
  }

  // 1. 分解:将数组分为两部分
  const mid = Math.floor(arr.length / 2);
  const left = arr.slice(0, mid);
  const right = arr.slice(mid);

  // 2. 递归地对左右两部分进行排序
  const sortedLeft = mergeSort(left);
  const sortedRight = mergeSort(right);

  // 3. 合并两个已排序的数组
  return merge(sortedLeft, sortedRight);
}

// 合并两个有序数组的函数
function merge(left, right) {
  let result = [];
  let i = 0;
  let j = 0;

  // 逐个比较两个数组的元素,将较小的元素加入结果数组
  while (i < left.length && j < right.length) {
    if (left[i] < right[j]) {
      result.push(left[i]);
      i++;
    } else {
      result.push(right[j]);
      j++;
    }
  }

  // 将剩余的元素添加到结果数组中
  return result.concat(left.slice(i)).concat(right.slice(j));
}

// 测试
const arr = [38, 27, 43, 3, 9, 82, 10];
console.log(mergeSort(arr));  // 输出:[3, 9, 10, 27, 38, 43, 82]
实现LazyMan
class LazyMan {
  constructor(name) {
    this.name = name;
    this.tasks = [];  // 存储待执行的任务
    // 初始任务:打印 "Hi I am name"
    this.tasks.push(() => console.log(`Hi I am ${name}`));
    // 使用 setTimeout 来处理异步任务
    setTimeout(() => this.next(), 0);
  }

  // 执行下一个任务
  next() {
    const task = this.tasks.shift();
    task && task();  // 执行任务队列中的第一个任务
  }

  // sleep 方法,延迟指定时间后执行
  sleep(time) {
    this.tasks.push(() => {
      setTimeout(() => {
        console.log(`Wake up after ${time}ms`);
        this.next();  // 延迟后执行下一个任务
      }, time);
    });
    return this;  // 返回当前对象,以便支持链式调用
  }

  // eat 方法,打印吃的东西
  eat(food) {
    this.tasks.push(() => {
      console.log(`Eat ${food}`);
      this.next();  // 执行下一个任务
    });
    return this;  // 返回当前对象,以便支持链式调用
  }
}

// 测试用例
new LazyMan('Tom').sleep(1000).eat('lunch').sleep(2000).eat('dinner');
请实现 DOM2JSON 一个函数,可以把一个 DOM 节点输出 JSON 的格式
function DOM2JSON(node) {
  // 如果节点是文本节点
  if (node.nodeType === Node.TEXT_NODE) {
    return node.nodeValue.trim();
  }

  // 如果是元素节点
  if (node.nodeType === Node.ELEMENT_NODE) {
    const json = {
      tagName: node.tagName.toLowerCase(),  // 标签名
      attributes: {},  // 存储元素的属性
      children: []  // 存储子节点
    };

    // 获取节点的属性并存入 attributes 对象
    for (let attr of node.attributes) {
      json.attributes[attr.name] = attr.value;
    }

    // 遍历并递归处理子节点
    for (let child of node.childNodes) {
      const childJson = DOM2JSON(child);
      if (childJson) {
        json.children.push(childJson);
      }
    }

    return json;
  }

  return null;  // 其他节点类型不处理
}

// 测试 DOM2JSON 函数
const testNode = document.querySelector('#test');  // 假设你有一个节点ID为 test
const result = DOM2JSON(testNode);
console.log(JSON.stringify(result, null, 2));
类数组拥有 length 属性 可以使用下标来访问元素 但是不能使用数组的方法 如何把类数组转化为数组?
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };

// 转换为数组
const array = Array.prototype.slice.call(arrayLike);

console.log(array);  // ['a', 'b', 'c']


const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };

// 转换为数组
const array = Array.from(arrayLike);

console.log(array);  // ['a', 'b', 'c']

const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };

// 转换为数组
const array = [...arrayLike];

console.log(array);  // ['a', 'b', 'c']
实现Object.is
function myObjectIs(a, b) {
  // 特殊规则:+0 和 -0 被认为是不同的
  if (a === b) {
    // 判断 +0 和 -0 不相等,特别需要处理这种情况
    return a !== 0 || 1 / a === 1 / b;
  }

  // 特殊规则:NaN 和 NaN 被认为是相等的
  return a !== a && b !== b;
}

// 测试
console.log(myObjectIs(2, 2));             // true
console.log(myObjectIs(2, '2'));           // false
console.log(myObjectIs(NaN, NaN));         // true
console.log(myObjectIs(0, -0));            // false
console.log(myObjectIs(+0, -0));           // false
console.log(myObjectIs(-0, -0));           // true
console.log(myObjectIs(null, undefined));  // false
console.log(myObjectIs(undefined, undefined)); // true
实现日期格式化函数
function formatDate(date, format) {
  const map = {
    'Y': date.getFullYear(),        // 年 (4位)
    'M': date.getMonth() + 1,       // 月 (1-12)
    'D': date.getDate(),            // 日 (1-31)
    'H': date.getHours(),           // 时 (0-23)
    'm': date.getMinutes(),         // 分 (0-59)
    's': date.getSeconds(),         // 秒 (0-59)
    'SS': date.getMilliseconds()    // 毫秒 (0-999)
  };

  // 替换格式化字符串中的日期字段
  return format.replace(/(Y|M|D|H|m|s|SS)/g, (match) => {
    // 如果是年,保留四位
    if (match === 'Y') {
      return map['Y'];
    }
    // 保证两位数字显示
    return map[match].toString().padStart(2, '0');
  });
}

// 示例
const now = new Date();
console.log(formatDate(now, 'Y-M-D H:m:s'));          // 输出示例: "2025-01-04 09:05:07"
console.log(formatDate(now, 'Y年M月D日 H时m分s秒'));   // 输出示例: "2025年01月04日 09时05分07秒"
console.log(formatDate(now, 'Y/M/D'));                // 输出示例: "2025/01/04"
console.log(formatDate(now, 'H:m:s.SS'));             // 输出示例: "09:05:07.012"
封装AJAX请求
红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?
function delay(time) {
  return new Promise((resolve) => setTimeout(resolve, time));
}

async function redLight() {
  console.log('红灯亮');
  await delay(3000); // 红灯亮 3 秒
}

async function greenLight() {
  console.log('绿灯亮');
  await delay(1000); // 绿灯亮 1 秒
}

async function yellowLight() {
  console.log('黄灯亮');
  await delay(2000); // 黄灯亮 2 秒
}

async function trafficLights() {
  while (true) {
    await redLight();   // 红灯
    await greenLight(); // 绿灯
    await yellowLight(); // 黄灯
  }
}

trafficLights();
如何用Promise实现图片的异步加载
function loadImage(src) {
  return new Promise((resolve, reject) => {
    const img = new Image(); // 创建一个图片对象
    img.onload = () => resolve(img); // 图片加载成功时,调用 resolve,并传入图片对象
    img.onerror = (err) => reject(err); // 图片加载失败时,调用 reject,并传入错误信息
    img.src = src; // 设置图片的来源
  });
}

// 使用示例
loadImage('https://example.com/image.jpg')
  .then((img) => {
    console.log('图片加载成功', img);
    document.body.appendChild(img); // 将图片添加到页面中
  })
  .catch((err) => {
    console.error('图片加载失败', err);
  });
类似 get-element-by-id 中划线转驼峰
function camelCase(str) {
  return str.replace(/([-_])([a-z])/g, function(match, group1, group2) {
    console.log({
      match,
      group1,
      group2
    });
    return group2.toUpperCase();
  });
}

console.log(camelCase("some-string_with-underscores"));
判断是否是电话号码
function isPhoneNumber(phone) {
  const phoneRegex = /^(\+?\d{1,3}[- ]?)?1[3-9]\d{9}$/; // 适用于中国手机号的规则
  return phoneRegex.test(phone);
}

// 示例
console.log(isPhoneNumber('13812345678')); // true
console.log(isPhoneNumber('+8613812345678')); // true
console.log(isPhoneNumber('1234567890')); // false
判断是否是邮箱
function isEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

// 示例
console.log(isEmail('example@example.com')); // true
console.log(isEmail('user.name+tag+sorting@example.com')); // true
console.log(isEmail('plainaddress')); // false
判断是否是身份证
function isIdCard(id) {
  const idCardRegex = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
  return idCardRegex.test(id);
}

// 示例
console.log(isIdCard('110101199001011234')); // true
console.log(isIdCard('11010119900101987X')); // true
console.log(isIdCard('123456789012345')); // false

难题

实现JSON.parse
利用JS对象来描述DOM结构,通过diff算法来更新视图
渲染百万条结构简单的大数据时 怎么使用分片思想优化渲染
虚拟DOM转真实DOM

juejin.cn/post/696871…

juejin.cn/post/684490…

juejin.cn/post/698390…

juejin.cn/post/735345…

数据结构与算法

数据结构与算法这部分个人认为作为一名程序员来说,不管个人对其抱着支持或者是抵触的态度,当前的事实就是大厂面试越来越看重这部分内容,尤其是毕业年限较短的同学,个人建议如果基础较薄弱或者零基础从没刷过题的,可以用三个月到半年(视学习强度而定)按照以下顺序把题都刷完,首先要知道算法这部分到底有哪些类型的题,每种题型有什么套路,经典题有哪些,最好能刷300题以上M难度的,吃透这些题,这样在面试大厂的时候,算法这部分就算是合格了。

推荐网站:《代码随想录》programmercarl.com/qita/langua…

推荐书籍:《剑指offer》,不是很厚,覆盖不到一百题常见面试题,涵盖各种题型,并且大多数题目都可以在leetcode上找到原题,如果数据结构与算法基础较薄弱的同学可以先用这本书学习,然后再刷下面的:

LeetCode 热题 100

LCR