JS手写题

实现 new 操作符

function myNew(fn, ...args) {
  // 创建一个新对象,该对象的原型指向构造函数的原型
  const obj = Object.create(fn.prototype);
  // 调用构造函数,将构造函数中的 this 绑定到新对象上
  const res = fn.call(obj, ...args);
  // 如果构造函数返回的是对象或者函数,则返回该对象;否则,返回新创建的对象
  return res !== null && (typeof res === "object" || typeof res === "function")
    ? res
    : obj;
}

例子如下:

// 构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;

  // return {
  //   name: "Picker",
  // };
  return () => {};
}

Person.prototype.say = function () {
  console.log(`Hi ${this.name}`);
};

const person = myNew(Person, "Christine", 28);
console.log("person.name: ", person.name);
console.log("person.age: ", person.age);
// person.say();
const person1 = new Person("Christine", 28);
console.log("person1.name: ", person.name);
console.log("person1.age: ", person.age);
// person1.say();

实现 call

Function.prototype.myCall = function (context, ...args) {
  if (!context) {
    context = window;
  }
  // 创建唯一的 key 值,作为我们构造的 context 内部的方法名
  const fn = Symbol();
  //this指向调用call的函数
  context[fn] = this;
  // 执行函数并返回结果 相当于把自身作为传入的context的方法进行调用了
  return context[fn](...args);
};

例子如下:

function say(value) {
  console.log(`${this.name} ${value}`);
}
const person = {
  name: "Christine",
};
say.myCall(person, "你好呀");

实现 apply

Function.prototype.myApply = function (context, args) {
  if (!context) {
    context = window;
  }
  const fn = Symbol();
  context[fn] = this;
  let result = null;
  if (args) {
    result = context[fn](...args);
  } else {
    result = context[fn]();
  }
  return result;
};

例子如下:

const arr = [1, 2, 3, 4];
console.log(Math.max.apply(null, arr));
console.log(Math.max.myApply(arr, arr));

实现 bind

Function.prototype.myBind = function (context, ...args) {
  if (!context) {
    context = window;
  }

  const fn = Symbol("boundFunction");

  const boundFunction = function (...innerArgs) {
    if (new.target) {
      // 使用 new 关键字调用时,将 this 指向新创建的对象
      this[fn] = boundFunction;
      this[fn](...args, ...innerArgs);
    } else {
      // 在普通函数调用时,将 this 指向指定的上下文
      context[fn](...args, ...innerArgs);
    }
  };

  // 设置原始函数的 prototype
  boundFunction.prototype = Object.create(this.prototype);

  // 将原始函数绑定到上下文的 symbol 属性上
  context[fn] = this;

  return boundFunction;
};

例子如下:

function Person(name, age) {
  console.log(name); //'我是参数传进来的name'
  console.log(age); //'我是参数传进来的age'
  console.log(this); //构造函数this指向实例对象
}
// 构造函数原型的方法
Person.prototype.say = function () {
  console.log(123);
};
let obj = {
  objName: "我是obj传进来的name",
  objAge: "我是obj传进来的age",
};
// 普通函数
function normalFun(name, age) {
  console.log(name); //'我是参数传进来的name'
  console.log(age); //'我是参数传进来的age'
  console.log(this); //普通函数this指向绑定bind的第一个参数 也就是例子中的obj
  console.log(this.objName); //'我是obj传进来的name'
  console.log(this.objAge); //'我是obj传进来的age'
}

// 作为构造函数调用;
let BindFun = Person.myBind(obj, "我是参数传进来的name");
let a = new BindFun("我是参数传进来的age");
a.say(); //123

// 作为普通函数调用;
let bindFun1 = normalFun.myBind(obj, "我是参数传进来的name");
bindFun1("我是参数传进来的age");

实现 flat

// 递归方式实现
function flatter(arr) {
  if (!arr.length) return [];
  return arr.reduce(
    (prev, cur) =>
      Array.isArray(cur) ? [...prev, ...flatter(cur)] : [...prev, cur],
    []
  );
}

// 迭代方式实现
function myFlatter(arr) {
  if (!arr.length) return;
  while (arr.some((item) => Array.isArray(item))) {
    arr = [].concat(...arr);
  }
  return arr;
}

例子如下:

const arr = [1, 2, [1, [2, 3, [4, 5, [6]]]]]
console.log(arr.flat(4));
console.log(myFlatter([1, 2, [1, [2, 3, [4, 5, [6]]]]]));

实现 Promise 及相关方法

class Promise {
  constructor(executor) {
    this.state = "pending";
    this.value = undefined;
    this.callbacks = [];

    const resolve = (value) => {
      if (this.state === "pending") {
        this.state = "fulfilled";
        this.value = value;
        this.callbacks.forEach((callback) => callback.onFulfilled(value));
      }
    };
    const reject = (reason) => {
      if (this.state === "pending") {
        this.state = "rejected";
        this.value = reason;
        this.callbacks.forEach((callback) => callback.onRejected(reason));
      }
    };

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

  then(onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      const fulfilledCallback = (value) => {
        try {
          if (typeof onFulfilled === "function") {
            const result = onFulfilled(value);
            resolve(result);
          }
        } catch (err) {
          reject(err);
        }
      };
      const rejectedCallback = (reason) => {
        try {
          if (typeof onRejected === "function") {
            const result = onRejected(reason);
            resolve(result);
          }
        } catch (err) {
          reject(err);
        }
      };
      if (this.state === "fulfilled") {
        setTimeout(() => {
          fulfilledCallback(this.value);
        }, 0);
      } else if (this.state === "rejected") {
        setTimeout(() => {
          rejectedCallback(this.value);
        }, 0);
      } else {
        this.callbacks.push({
          onFulfilled: fulfilledCallback,
          onRejected: rejectedCallback,
        });
      }
    });
  }
  static all(promises) {
    const result = [];
    let fulfilledCount = 0;
    return new Promise((resolve, reject) => {
      promises.forEach((promise, index) => {
        if (!(promise instanceof Promise)) {
          promise = Promise.resolve(promise);
        }
        promise
          .then((value) => {
            result[index] = value;
            fulfilledCount++;
            if (fulfilledCount === promises.length) {
              resolve(result);
            }
          })
          .catch(reject);
      });
    });
  }
  static race(promises) {
    return new Promise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(resolve).catch(reject);
      });
    });
  }
}

例子如下:

// promiseA+ (resolve, reject) => {} 是executor
const p = new Promise((resolve, reject) => {
  // resolve 和 reject 是 Promise 内部实现好的函数
  // 这里的代码是立即执行的

  // reject('失败'); // 将状态从 pending 改成了 rejected (状态凝固)
  // setTimeout(() => {
  //   resolve(200); // 将状态从 pending 改成了 fulfilled
  // }, 1000);
  // reject('500')
  resolve(new Promise((resolve, reject) => {
    resolve(200)
  }))
})

p.then(res => {
  console.log('res: ', res);
})

// p.then(null, err => {
//   throw new Error(err)
// }).then(res => {
//   console.log('res: ', res);
// }, error => {
//   console.log('error: ', error);
// })

// onFulFilled => p成功后,调用的回调
// onRejected =>  p失败后,调用的回调
// .catch可以全局捕获错误
// p.then(onFulFilled, onRejected).catch()
// p.then(res => {
//   console.log('res: ', res);
//   // return '成功'
//   return new Promise((resolve, reject) => {
//     resolve(200)
//   })
// }, err => {
//   console.log('err: ', err);
//   return '失败'
//   return new Promise((resolve, reject) => {
//     reject(404)
//     // resolve(404)
//   })
// }).then(res => console.log('===', res), error => console.log(error));

// 链式调用2个核心要点
// 1. 上一个 .then 要返回一个 promise 对象
// 2. 下一个 .then 的参数要拿到上一个 .then 回调的返回值


// 循环引用
// const p2 = p.then(res => {
//   console.log('res: ', res);
//   setTimeout(() => {
//     return p2
//   }, 0);
// }, error => {
//   console.log(error)
//   return p2
// });

// Promise.resolve(4).then(res => console.log('===', res))
// Promise.reject('静态方法reject').then(res => console.log('===', res), error => console.log('error', error))

// const p3 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(3000)
//   }, 3000);
// })

// const p4 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(1000)
//   }, 1000);
// })

// const p5 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     resolve(2000)
//   }, 2000);
// })

// const p6 = new Promise((resolve, reject) => {
//   resolve(666)
// })

// Promise.all([p3, p4, p5, p6]).then(res => console.log('===', res))
// Promise.race([p3, p4, p5, p6]).then(res => console.log('===', res))
// Promise.race([Promise.reject('第一个被返回'), p3, p4, p5, p6]).then(res => console.log('===', res), error => console.log('error', error))
// Promise.race([Promise.resolve('第一个被返回'), p3, p4, p5, p6]).then()

实现 reduce

Array.prototype.myReduce = function (callBack, initialValue) {
  if (!Array.isArray(this)) {
    throw new TypeError("调用者类型错误");
  }

  const { length } = this;

  // 检查是否提供了初始值
  let accumulator =
    typeof initialValue === "undefined" ? this[0] : initialValue;

  // 从索引 0 或 1 开始遍历数组
  for (let i = typeof initialValue === "undefined" ? 1 : 0; i < length; i++) {
    accumulator = callBack.call(undefined, accumulator, this[i], i, this);
  }

  return accumulator;
};

例子如下:

// 使用 myReduce 求和
const sum = numbers.myReduce((acc, current) => acc + current);
const sum1 = numbers.myReduce((acc, current) => acc + current, 0);
console.log(sum); // 输出: 15
console.log(sum1); // 输出: 15

// 使用 myReduce 求积
const product = numbers.myReduce((acc, current) => acc * current);
const product1 = numbers.myReduce((acc, current) => acc * current, 1);
console.log(product); // 输出: 120
console.log(product1); // 输出: 120

实现深拷贝

function deepClone(obj, clones = new WeakMap()) {
  // 基本数据类型或者 null 或者函数直接返回
  const type = typeof obj;
  if (obj === null || type !== "object" || type === "function") {
    return obj;
  }

  // 处理循环引用
  if (clones.has(obj)) {
    return clones.get(obj);
  }
  // 处理日期
  if (obj instanceof Date) {
    return new Date(obj);
  }
  // 处理正则
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }
  // 处理 Symbol
  if (typeof obj === "symbol") {
    // 将 obj 视为 Symbol 对象,并调用其 valueOf 方法获取原始的 Symbol 值。
    return Object(Symbol.prototype.valueOf.call(obj));
  }
  // 处理数组和对象
  const cloneObj = Array.isArray(obj) ? [] : {};

  // 将当前对象引用存入 WeakMap
  clones.set(obj, cloneObj);

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], clones);
    }
  }

  return cloneObj;
}

例子如下:

const obj = {
  age: 18,
  name: "Christine",
  date: new Date(),
  hh: /hh/,
  fun: console.log,
  hobbies: ["吃饭", "睡觉", "打豆豆"],
  oo: {
    son: "berry",
  },
  [Symbol("1")]: 1,
  nul: null,
  und: undefined,
  symbol: Symbol(234),
};
obj.aa = obj; // 循环引用
const clonedObj = deepClone(obj);
console.log(clonedObj);

实现防抖

  • 防抖的目标是确保在一段连续的触发事件之后,只执行一次函数。
  • 当触发事件的间隔小于设定的时间间隔时,重新计时,不执行函数。
  • 最终只在触发事件后的指定时间间隔内执行一次函数。

用途:用户输入搜索,只在用户停止输入一段时间后执行搜索请求。

// 防抖
function debounce(fn, delay) {
  // 默认 300 毫秒
  let timer;
  return (...args) => {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn(...args);
    }, delay);
  };
}

例子如下:

// 假设有一个需要防抖的函数
function handleInput() {
  console.log("Input event handled");
}

// 使用防抖函数创建一个新的函数
const debouncedInputHandler = debounce(handleInput, 500);

// 模拟输入事件,如果在 500 毫秒内再次调用,之前的计时器会被清除,重新开始计时,确保在最后一次调用后的 500 毫秒后执行真正的处理函数。
debouncedInputHandler();
debouncedInputHandler();
debouncedInputHandler(); // 会在最后一次输入后的500毫秒执行

实现节流

  • 节流的目标是控制函数在一段时间内执行的频率,确保函数每隔一定时间执行一次。
  • 当触发事件时,如果距离上一次执行函数的时间间隔小于设定的时间间隔,则忽略该触发事件。
  • 最终是按照设定的时间间隔执行函数。

用途:滚动事件,确保滚动事件不会以过快的速度触发;窗口大小改变的事件可能在短时间内频繁触发,因此通常会选择使用节流来限制触发的频率

function throttle(fn, delay) {
  let flag = true;
  let timer = null;
  return () => {
    if (!flag) {
      return;
    }
    flag = false;
    timer = setTimeout(() => {
      fn();
      flag = true;
    }, delay);
  };
}

例子如下:

const throttledResizeHandler = throttle(function () {
  // 处理窗口大小改变的操作
  console.log("Window resized");
}, 200);

window.addEventListener("resize", throttledResizeHandler);

实现 compose

function compose(...fns) {
  if (fns.length === 0) {
    return (v) => v;
  }

  if (fns.length === 1) {
    return fns[0];
  }

  return fns.reduce(
    (prev, curr) =>
      (...args) =>
        prev(curr(...args))
  );
}

例子如下:

function fn1(x) {
  return x + 1;
}

function fn2(x) {
  return x + 2;
}

function fn3(x) {
  return x + 3;
}

function fn4(x) {
  return x + 4;
}

const result = compose(fn1, fn2, fn3, fn4);
result(1);

实现发布订阅模式

class EventEmitter {
  constructor() {
    this.events = {};
  }

  // 添加订阅
  on(type, handler) {
    if (!this.events[type]) {
      this.events[type] = [handler];
    } else {
      this.events[type].push(handler);
    }
  }

  // 删除订阅
  off(type, handler) {
    if (!this.events[type]) {
      return;
    }
    this.events[type] = this.events[type].filter((item) => item !== handler);
  }
  // 触发事件
  emit(type, ...args) {
    if (!this.events[type]) {
      return;
    }
    this.events[type].forEach((fn) => fn.apply(this, args));
  }
  // 只执行一次订阅事件
  once(type, handler) {
    function onceWrapper(...args) {
      // 调用原始的处理函数
      handler.apply(this, args);
      // 在触发后立即移除订阅
      this.off(type, onceWrapper);
    }
    this.on(type, onceWrapper);
  }
}

例子如下:

const eventEmitter = new EventEmitter();
const handler = (...rest) => {
  console.log(rest);
};

const handleClick = (...args) => {
  console.log("===", args);
};

const handleScroll = (...args) => {
  console.log("++++++++++++++++", args);
};
eventEmitter.on("click", handler);
eventEmitter.on("click", handleClick);

eventEmitter.on("change", handler);

eventEmitter.emit("click", 1, 2, 3);

eventEmitter.once("click", handler);

eventEmitter.emit("click", "Hi Christine");

eventEmitter.once("scroll", handleScroll);
eventEmitter.emit("scroll", "Hi Berry");
eventEmitter.emit("scroll", "Hi Picker");

实现 Scheduler

Scheduler 是一个带并发限制的异步调度器,保证同时运行的任务最多有n个,n 为可配置的

题目描述

解释下完整的执行流程:一开始1、2两个任务开始执行;
500ms时,2任务执行完毕,输出2,任务3开始执行;
800ms时,3任务执行完毕,输出3,任务4开始执行;
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行;
1200ms时,4任务执行完毕,输出4。
class Scheduler {
  constructor(limit) {
    this.queue = [];
    this.maxRunCounts = limit;
    this.runCounts = 0;
  }

  // 添加任务到队列
  add(time, value) {
    const promiseCreator = () => {
      return new Promise(function (resolve, reject) {
        setTimeout(() => {
          console.log(value);
          resolve();
        }, time);
      });
    };
    this.queue.push(promiseCreator);
  }

  request() {
    if (!this.queue.length || this.runCounts > this.maxRunCounts) {
      return;
    }
    this.runCounts++;
    const shift = this.queue.shift();
    shift().then(() => {
      this.runCounts--;
      this.request();
    });
  }

  taskStart() {
    for (let i = 0; i < this.maxRunCounts; i++) {
      this.request();
    }
  }
}

例子如下:

const scheduler = new Scheduler(2);

const addTask = (time, value) => {
  scheduler.add(time, value);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();

模拟实现 setInterval

function simulateSetInterval(callback, timeout) {
  let timer = null;
  function fn() {
    timer = setTimeout(() => {
      callback();
      fn();
    }, timeout);
  }
  fn();
  return () => clearInterval(timer);
}

例子如下:

const a = simulateSetInterval(() => {
  console.log("Hi Christine");
}, 1000);

const b = simulateSetInterval(() => {
  console.log("Hi Berry");
}, 1000);

模拟实现 settimeout

function simulateSetTimeout(callback, timeout) {
  const timer = setInterval(() => {
    clearInterval(timer);
    callback();
  }, timeout);
}

例子如下:

simulateSetTimeout(() => {
  console.log("Hi Christine");
}, 1000);

实现Object.is

Object.newIs = (x, y) => {
  // 当 x 和 y 都是 +0 和 -0 时,通过比较它们的倒数来判断它们是否相等。
  if (x === 0 && y === 0) {
    return 1 / x === 1 / y;
  }

  // 当 x 和 y 都是 NaN 时,直接返回 true。
  if (isNaN(x) && isNaN(y)) {
    return true;
  }

  // 其他情况使用严格相等运算符
  return x === y;
};

例子如下:

console.log(Object.newIs(-0, +0)); // false
console.log(Object.newIs(-0, 0)); // false
console.log(Object.newIs(+0, 0)); // true
console.log(Object.newIs(NaN, NaN)); // true
console.log(Object.newIs(5, 5)); // true

冒泡排序

const arr = [3, 8, 2, 6, 4, 10, 1];
function bubbleSort(arr) {
  const { length } = arr;
  let flag = true;
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
        flag = false;
      }
    }
    if (flag) {
      break;
    }
  }
  return arr;
}

console.log(bubbleSort(arr));

选择排序

const arr = [3, 8, 2, 6, 4, 10, 1];
function selectionSort(arr) {
  const { length } = arr;
  for (let i = 0; i < length - 1; i++) {
    let minIndex = i;

    for (let j = i + 1; j < length; j++) {
      if (arr[j] < arr[minIndex]) {
        // 寻找最小的数
        minIndex = j; // 将最小数的索引保存
      }
    }
    [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
  }

  return arr;
}

// console.log(selectionSort(arr));

// 选择排序的关键是循环遍历数组,每次找到当前范围的最小值,把它放在当前范围的头部;然后缩小排序范围,继续重复以上操作

function test(arr) {
  const { length } = arr;
  // 定义 minIndex,缓存当前区间最小值的索引,注意是索引
  let minIndex;
  // i 是当前排序区间的起点
  for (let i = 0; i < length - 1; i++) {
    // 初始化 minIndex 为当前区间第一个元素
    minIndex = i;
    // i、j分别定义当前区间的上下界,i是左边界,j是右边界
    for (let j = i; j < length; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j;
      }
    }

    // 如果 minIndex 对应元素不是目前的头部元素,则交换两者
    if (minIndex !== i) {
      [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
    }
  }

  return arr;
}

console.log(test(arr));

插入排序

const arr = [3, 8, 2, 6, 4, 10, 1];

function insertionSort(arr) {
  const { length } = arr;
  for (let i = 1; i < length; i++) {
    let prevIndex = i - 1,
      currArr = arr[i];

    while (prevIndex >= 0 && arr[prevIndex] > currArr) {
      arr[prevIndex + 1] = arr[prevIndex]; // 将元素移动到下一位
      prevIndex--;
    }

    arr[prevIndex + 1] = currArr;
  }

  return arr;
}

console.log(insertionSort(arr));

function insertSort(arr) {
  const { length } = arr;

  for (let i = 1; i < length; i++) {
    let j = i;
    // 缓存当前元素
    let cur = arr[i];
    // 判断 j 前一个元素是否比当前元素大
    while (j > 0 && arr[j - 1] > cur) {
      // 如果是,则将 j 前面的一个元素后移一位,为 cur 让出位置
      arr[j] = arr[j - 1];
      j--;
    }
    // 循环让位,最后得到的 j 就是 cur 的正确索引
    arr[j] = cur;
  }
}

快速排序

const arr = [3, 8, 2, 6, 4, 10, 1];

function quickSort(arr) {
  function sort(arr, start, end) {
    if (start >= end) {
      return;
    }
    let base = arr[start];
    let i = start,
      j = end;
    while (i < j) {
      // 从右往左挪动 j 直到找到比 base 小的值停下来
      while (i < j && arr[j] >= base) {
        j--;
      }

      // 从左往右挪动 i 直到找到比 base 大的值停下来
      while (i < j && arr[i] <= base) {
        i++;
      }

      if (i < j) {
        // 交换找到的符合条件的两个值
        [arr[i], arr[j]] = [arr[j], arr[i]];
      }
    }
    // 如果 i 和 j 值相同,将与 base 交换位置
    [arr[start], arr[i]] = [arr[i], arr[start]];
    sort(arr, start, i - 1);
    sort(arr, i + 1, end);
  }
  sort(arr, 0, arr.length - 1);
  return arr;
}

console.log(quickSort(arr));

归并排序

二分查找

列表转成树形结构

function listToTree(list) {
  const map = {};
  const tree = [];
  // 创建节点映射
  for (const item of list) {
    map[item.id] = item;
  }

  // 构建树
  for (const item of list) {
    if (item.parent_id === null) {
      tree.push(item);
    } else {
      const parent = map[item.parent_id];
      if (!parent.children) {
        parent.children = [];
      }
      parent.children.push(item);
    }
  }

  return tree;
}

例子如下:

const list = [
  { id: 1, parent_id: null, label: "Node 1" },
  { id: 2, parent_id: 1, label: "Node 1.1" },
  { id: 3, parent_id: 1, label: "Node 1.2" },
  { id: 4, parent_id: 2, label: "Node 1.1.1" },
  { id: 5, parent_id: 2, label: "Node 1.1.2" },
  { id: 6, parent_id: null, label: "Node 2" },
  { id: 7, parent_id: 6, label: "Node 2.1" },
];

listToTree(list)

树形结构转成列表

题目描述

const tree = [
  {
    id: 1,
    parent_id: null,
    label: "Node 1",
    children: [
      {
        id: 2,
        parent_id: 1,
        label: "Node 1.1",
        children: [
          { id: 4, parent_id: 2, label: "Node 1.1.1", children: [] },
          { id: 5, parent_id: 2, label: "Node 1.1.2", children: [] },
        ],
      },
      { id: 3, parent_id: 1, label: "Node 1.2", children: [] },
    ],
  },
  {
    id: 6,
    parent_id: null,
    label: "Node 2",
    children: [{ id: 7, parent_id: 6, label: "Node 2.1", children: [] }],
  },
];

转成

const list = [
  { id: 1, parent_id: null, label: "Node 1" },
  { id: 2, parent_id: 1, label: "Node 1.1" },
  { id: 3, parent_id: 1, label: "Node 1.2" },
  { id: 4, parent_id: 2, label: "Node 1.1.1" },
  { id: 5, parent_id: 2, label: "Node 1.1.2" },
  { id: 6, parent_id: null, label: "Node 2" },
  { id: 7, parent_id: 6, label: "Node 2.1" },
];

代码实现

// 递归
function treeToList(tree) {
  const list = [];

  const dfs = (node) => {
    const { id, label, parent_id } = node;
    list.push({ id, parent_id, label });

    if (node.children && node.children.length) {
      for (const child of node.children) {
        dfs(child);
      }
    }
  };

  tree.forEach((node) => {
    dfs(node);
  });

  return list;
}

// 迭代
function treeToList(tree) {
  const list = [];
  // 初始状态将树的根节点放入栈中
  const stack = [...tree];
  while (stack.length) {
    const node = stack.shift();
    // 添加节点信息到列表中
    list.push({
      id: node.id,
      parent_id: node.parent_id,
      label: node.label,
    });

    if (node.children && node.children.length) {
      stack.push(...node.children);
    }
  }
  return list;
}

例子如下:

const tree = [
  {
    id: 1,
    parent_id: null,
    label: "Node 1",
    children: [
      {
        id: 2,
        parent_id: 1,
        label: "Node 1.1",
        children: [
          { id: 4, parent_id: 2, label: "Node 1.1.1", children: [] },
          { id: 5, parent_id: 2, label: "Node 1.1.2", children: [] },
        ],
      },
      { id: 3, parent_id: 1, label: "Node 1.2", children: [] },
    ],
  },
  {
    id: 6,
    parent_id: null,
    label: "Node 2",
    children: [{ id: 7, parent_id: 6, label: "Node 2.1", children: [] }],
  },
];

console.log(treeToList(tree));

实现一个对象的 flatten 功能

题目描述

const obj = {
  a: {
    b: 1,
    c: 2,
    d: { e: 5 },
  },
  b: [1, 3, { a: 2, b: 3 }],
  c: 3,
};

将上面对象处理成如下格式

// {
//  'a.b': 1,
//  'a.c': 2,
//  'a.d.e': 5,
//  'b[0]': 1,
//  'b[1]': 3,
//  'b[2].a': 2,
//  'b[2].b': 3
//   c: 3
// }

代码实现

function flatten(obj) {
  const res = {};

  function isObject(val) {
    return typeof val === "object" && val !== null;
  }

  const dfs = (cur, prefix) => {
    if (!isObject(cur)) {
      res[prefix] = cur;
    } else {
      if (Array.isArray(cur)) {
        cur.forEach((item, index) => {
          dfs(item, `${prefix}[${index}]`);
        });
      } else {
        for (const [key, value] of Object.entries(cur)) {
          dfs(value, `${prefix}${prefix ? "." : ""}${key}`);
        }
      }
    }
  };

  dfs(obj, "");
  return res;
}

例子如下:

flatten(obj)

实现版本号排序

有一组版本号如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。现在需要对其进行排序,排序的结果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']

const versions = ["0.1.1", "2.3.3", "0.302.1", "4.2", "4.3.5", "4.3.4.5"];

versions.sort((version1, version2) => {
  const arr1 = version1.split(".").map(Number);
  const arr2 = version2.split(".").map(Number);
  const len = Math.max(arr1.length, arr2.length);
  for (let i = 0; i < len; i++) {
    const num1 = arr1[i] || 0;
    const num2 = arr2[i] || 0;

    if (num1 !== num2) {
      return num2 - num1;
    }
  }
  // 如果循环结束后都没有发现不同的部分,说明两个版本号是相等的,返回 0。
  return 0;
});

实现虚拟 dom 转换为真实 dom

题目描述

const virtualDom = {
  tag: "DIV",
  attrs: {
    id: "app",
    class: "container",
  },
  children: [
    "some text", // 文本节点
    {
      tag: "SPAN",
      attrs: {
        class: "span",
      },
      children: [{ tag: "A", children: [] }],
    },
    {
      tag: "SPAN",
      attrs: {
        class: "span",
      },
      children: [
        { tag: "A", children: [] },
        { tag: "A", children: [] },
      ],
    },
  ],
};
转成
<div id="app" class="container">
  some text
  <span>
    <a></a>
  </span>
  <span> 
    <a></a>
    <a></a>
  </span>
</div>
function virtualDomTransformRealDom(virtualDom) {
  // 文本节点
  if (typeof virtualDom === "string") {
    return document.createTextNode(virtualDom);
  }

  const { tag, attrs, children } = virtualDom;
  const element = document.createElement(tag);

  // 设置属性
  if (attrs && Object.entries(attrs).length) {
    for (const [attrName, attrValue] of Object.entries(attrs)) {
      element.setAttribute(attrName, attrValue);
    }
  }

  // 递归处理子节点
  for (let child of virtualDom.children) {
      const childElement = virtualDomTransformRealDom(child);
      element.appendChild(childElement);
  }
  return element;
}

例子如下:

const virtualDom = {
  tag: "DIV",
  attrs: {
    id: "app",
    class: "container",
  },
  children: [
    "some text", // 文本节点
    {
      tag: "SPAN",
      attrs: {
        class: "span",
      },
      children: [{ tag: "A", children: [] }],
    },
    {
      tag: "SPAN",
      attrs: {
        class: "span",
      },
      children: [
        { tag: "A", children: [] },
        { tag: "A", children: [] },
      ],
    },
  ],
};

// 转换虚拟 DOM 到真实 DOM
const realDom = virtualDomTransformRealDom(virtualDom);
console.log("realDom: ", realDom);

// 将真实 DOM 插入到页面中
document.body.appendChild(realDom);

实现模板字符串解析功能

function parseTemplate(template, data) {
  // const regex = /{{(.*?)}}/g;
  const regex = /\{\{(\w+)\}\}/g;

  return template.replace(regex, (match, key) => {
    console.log("match: ", match, key); // {{name}} name
    const value = data[key.trim()];
    return value;
  });
}

例子如下

const template = "我是{{name}},年龄{{age}},性别{{sex}}";
const data = {
  name: "Christine",
  age: 18,
};
console.log(parseTemplate(template, data)); // 我是Christine,年龄18,性别undefined

实现 Query 链式调用

核心在于返回 this 才能支持链式调用

class Query {
  constructor(data) {
    this.data = data;
    this.collection = [];
  }

  where(callBack) {
    this.collection = this.data.filter(callBack);
    return this;
  }

  sortBy(callBack) {
    this.collection.sort(callBack);
    return this;
  }

  groupBy(key) {
    const map = {};
    for (let i = 0; i < this.collection.length; i++) {
      const item = this.collection[i];
      const value = map[item[key]] ? map[item[key]] : [];
      value.push(item);
      map[item[key]] = value;
    }
    this.collection = map;
    return this;
  }

  execute() {
    return this.collection;
  }
}

例子如下

// 示例数据
const data = [
  { id: 1, name: "Alice", age: 25 },
  { id: 2, name: "Bob", age: 30 },
  { id: 3, name: "Charlie", age: 20 },
  { id: 4, name: "David", age: 25 },
  { id: 5, name: "Eve", age: 30 },
];

// 示例代码
const result = new Query(data)
  .where((item) => item.age > 20)
  .sortBy((a, b) => a.age - b.age)
  .groupBy("age")
  .execute();

实现数组去重(要求最佳时间复杂度)

function test(arr) {
  const map = new Map();
  for (let i = arr.length - 1; i >= 0; i--) {
    if (map.has(arr[i])) {
      arr.splice(i, 1);
    } else {
      map.set(arr[i], true);
    }
  }
  return arr;
}

console.log(test([1, 7, 2, 5, 3, 2, 7, 5, 6, 8]));

实现获取数组第二大的数(要求时间复杂度 O(n))

function getSecondValue(arr) {
  if (arr.length < 2) {
    return null;
  }
  let max = arr[0];
  let second = arr[0];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] > max) {
      second = max;
      max = arr[i];
    } else if (arr[i] > second) {
      second = arr[i];
    }
  }
  return second;
}

console.log(getSecondValue([1, 2, 4, 6, 5, 7]));