VuePressVuePress
vue2
vue3
React
css
javascript
实操题目
http
真题
事件循环
题目
vue2
vue3
React
css
javascript
实操题目
http
真题
事件循环
题目

实现 用户输入的 撤销/重做

1

<input placeholder="..." id="input"/>
<button id="undo">撤销</button>
<button id="redo">重做</button>

const undoEl = document.getElementById('undo')
const redoEl = document.getElementById('redo')
const inputEl = document.getElementById('input')
let list = []
let index = 0

// input
inputEl.addEventListener('change', (e)=> {
  const inputValue = e.detail.value
  list.push(inputValue)
  index++
})

// 重做, 则是删除
redoEl.addEventListener('click', ()=> {
  list.length = index
  index--
})

// 撤销
redoEl.addEventListener('click', ()=> {
  if(index<=0) return
  index--
  inputEl.value = list[index]
})

// 代码不对,需要自己写

手写 class 的方法

现有三种菜单:button 类型,select 类型,modal 类型。 • 共同特点

  1. title icon 属性
  2. isDisabled 方法(可直接返回 false)
  3. exeC 方法,执行菜单的逻辑

• 不同点

  1. button 类型,执行 exec 时打印'hello'
  2. select 类型,执行 exec 时返回一个数组 ['item', 'item2', 'item3']
  3. modal 类型,执行 exec 时返回一个 DOM Element
    modal

用 ES6 语法写出这三种菜单的 class

class BaseMenu {
  constructor(title, icon) {
    this.title = title;
    this.icon = icon;
  }
  isDisabled() {
    return false;
  }
}

class ButtonMenu extends BaseMenu {
  constructor(title, icon) {
    super(title, icon);
  }
  exec() {
    console.info("hello");
  }
}

class SelectMenu extends BaseMenu {
  constructor(title, icon) {
    super(title, icon);
  }
  exec() {
    console.info(["item", "item2", "item3"]);
  }
}

class ModalMenu extends BaseMenu {
  constructor(title, icon) {
    super(title, icon);
  }
  exec() {
    const dom = document.createElement("div");
    dom.innerText = "modal";
    return dom;
  }
}

假定一个全局函数 getLocation、可异步获取地理位置

  • 成功返回{ errno: 0, data: { x: 100, y: 200 } }
  • 失败返回{ errno: 1, msg: '错误信息’} 封装 useLocation
  • 在 onMounted 时获取并显示
  • 考虑 loading error 情况
import { onMounted, reactive } from "vue";

function getLocation(code) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (code == 200) {
        resolve({ errno: 0, data: { x: 100, y: 200 } });
      } else {
        resolve({ errno: 1, msg: "错误信息" });
      }
    }, 1000);
  });
}

function useLocation() {
  const info = reactive({
    loading: false,
    data: null,
    msg: "",
  });

  onMounted(async () => {
    info.loading = true;
    const result = await getLocation(200);
    if (result && result.errno === 0) {
      info.data = result.data;
    } else {
      info.msg = result.msg;
    }
    info.loading = false;
  });
  return toRefs(info);
}

// 在vue中用法
export default {
  setup() {
    const { loading, data, msg } = useLocation();
  },
};

用 js 实现 入队, 出队,长度

  • 栈 入栈 出栈 先进后出
  • 队列 入队 出队 先进先出

1

// ...

数组去重复

// 方法1
const arr = [1, 2, 3, 4, 3, 4, 1, 6]
new Set([...arr])

// 方法2
const arr = [1, 2, 3, 4, 3, 4, 1, 6]
function unique(array) {
  const newArr = []
  array.forEach(element => {
      if(newArr.indexOf(element) === -1) {
        newArr.push(element)
      }
  });
  console.info(333, newArr)
}
unique(arr)

// 方法3
const arr = [1, 2, 3, 4, 3, 4, 1, 6]
function unique(array) {
  const newArr = arr.reduce((cur, item)=>{
    if(cur.indexOf(item) === -1) {
      cur.push(item)
    }
    return cur
  }, [])
  console.info(333, newArr)
}
unique(arr)

// 方法4
const arr = [1, 2, 3, 4, 3, 4, 1, 6]
function unique(array) {
  const info = new Map()
  for (const item of array) {
     info.set(item, item)
  }
  console.info(99,info, Array.from(info.keys()))
}
unique(arr)

// 方法5
const arr = [1, 2, 3, 4, 3, 4, 1, 6]
function unique(array) {
  const data = array.filter((item, index)=> arr.indexOf(item) === index)
  console.info(9, data)
}
unique(arr)

俩个数组的交集和并集

const arr1 = [1, 2, 3, 4];
const arr2 = [3, 2, 6, 9];

// 交集
function getIntersection(arr1, arr2) {
  // arr1.filter((c) => arr2.includes((c)));
  let result = [];
  for (let i = 0; i < arr1.length; i++) {
    for (let j = 0; j < arr2.length; j++) {
      if (arr1[i] == arr2[j]) {
        result.push(arr1[i]);
      }
    }
  }
  console.info(result);
  return result;

  // let set1 = new Set();
  // let set2 = new Set(arr2);
  // for (let item of arr1) {
  //   if (set2.has(item)) {
  //     set1.add(item);
  //   }
  // }
  // return Array.from(set1);
}

//  function fn1(arr1, arr2){
//   let newArr = []
//   newArr = arr1.filter(c=> arr2.includes(c))
//   return newArr
//  }

console.info(getIntersection(arr1, arr2));

// 并集
function getUnion() {
  const result = [...new Set([...arr1, ...arr2])];

  // const set2 = new Set(arr2);
  // for (let item of arr1) {
  //   set2.add(item);
  // }
  // return Array.from(set2);
}

// 差集 (数组的差集是指在第一个数组中有但在第二个数组中没有的元素。)
const arr1 = [1, 2, 3, 4];
const arr2 = [3, 2, 6, 9];
function diff(arr1, arr2) {
  return arr1.filter((c) => !arr2.includes(c));
}
console.info(123, diff(arr1, arr2));

// 补集 ( 数组的补集是指在全集中有但在指定数组中没有的元素。其中,全集是指两个数组的并集。)
const arr1 = [1, 2, 3, 4];
const arr2 = [3, 2, 6, 9];
function comp(arr1, arr2) {
  // 交集
  const newArr = getIntersection(arr1, arr2);
  // 并集
  const unionArr = union(arr1, arr2);

  return unionArr.filter((c) => !newArr.includes(c));
}
console.info(123, comp(arr1, arr2));

判断俩个数据之和是 n

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const n = 8;

function getNumberTotalByTwo(arr, n) {
  let res = [];
  for (let i = 0; i < arr.length - 1; i++) {
    let flug = false;
    const a = arr[i];
    for (let j = i + 1; j < arr.length; j++) {
      const b = arr[j];
      if (a + b === n) {
        flug = true;
        res.push(a);
        res.push(b);
        break;
      }
    }
    if (flug) break;
  }
  console.info(1, res);
}

判断三个数据之和是 n

⾸先我们设置⼀个固定的数,然后在设置两个指针,左指针指向固定的数的后⾯那个值, 右指针指向最后⼀个值,两个指针相向⽽⾏。

每移动⼀次指针位置,就计算⼀下这两个指针与固定值的和是否为 target, 如果是,那么我们就得到⼀组符合条件的数组,如果不是 target,就有⼀下两种情况: 相加之和⼤于 target,说明右侧值⼤了,右指针左移; 相加之和⼩于 target,说明左侧值⼩了,左指针右移;

function allNum(nums, target){
   let result = []
   nums.sort((a,b) => a - b)
   for(let i = 0; i < nums.length - 2; i++) {
      let left = i + 1
      let right = nums.length-1

      while(left < right) {
         const sum = nums[left] + nums[i] + nums[right]
         if( sum === target ) {
            console.info(999,[nums[left], nums[i], nums[right]] )
            result.push([nums[left], nums[i], nums[right]])
            left++
            right--
         } else if ( sum < target) {
            left++
         } else {
            right--
         }
      }
   }
   return result
}

const arr = [-1, 0, 1, 2, -1, -4]          
console.log(allNum(arr, -1));

写出下列数组的排序过程

const arr = [2, 1, 9, 7, 8, 6, 3, 5];

1

快速排序 (去重)

function quickSort(arr) {
  let length = arr.length - 1;
  if (length <= 1) return arr;

  const midIndex = Math.floor(length / 2); // 中间的index
  const midValue = arr.splice(midIndex, 1)[0];
  // 中间的value,修改原先的arr的结构, 将midValue单独抽离出来

  const left = [],
    right = [];

  // 注意这里使用 arr.length arr被修改了
  for (let i = 0; i < arr.length; i++) {
    const n = arr[i];
    if (n < midValue) {
      // 小于放在左边
      left.push(n);
    } else {
      right.push(n);
    }
  }

  return quickSort(left).concat([midValue], quickSort(right));
}

const arr = [2, 1, 9, 7, 8, 6, 3, 5];
quickSort(arr);

快速排序

const arr = [4,2,6,3,2,5,8,1,3,2,5,8,4]
function quicksort(array) {
  if(array.length === 0) return array
  const midIndex = Math.floor(array.length / 2)
  const midVal = array.splice(midIndex, 1)[0]

  let leftVal = [], rightVal = []
    for (let index = 0; index < array.length; index++) {
      const val = array[index];
      if(midVal > val) {
        leftVal.push(val)
      }else {
        rightVal.push(val)
      }
    }
  return quicksort(leftVal).concat([midVal],quicksort(rightVal) )
}

console.info(33, quicksort(arr))

冒泡排序

const arr = [1, 9, 4, 3, 6, 5, 7, 8, 2];

function sortArr(arr) {
  let temp = 0;
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  console.info("arr", arr);
}

sortArr(arr);
// 优化版本
const arr = [1, 9, 4, 3, 6, 5, 7, 8, 2];

function sortArr(arr) {
  let temp = 0;
  let flug = false;
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
        flug = true;
      }
    }
    if (!flug) break; // 没有发生改变
  }
  console.info("arr", arr);
}

sortArr(arr);

判断字符中出现最多的字符和次数

let str = "dasdsdsadasdaaaaaasa21glda";
function getStrCountAndKey(str) {
  let obj = new Map();

  Array.from(str).forEach((c) => {
    if (!obj.has(c)) {
      obj.set(c, 0);
    }
    obj.set(c, obj.get(c) + 1);
  });
  console.info(333, obj);

  let maxCount = 0;
  let maxChar = "";
  obj.forEach((value, key) => {
    if (value > maxCount) {
      maxCount = value;
      maxChar = key;
    }
  });

  console.info(maxCount, maxChar);
}
getStrCountAndKey(str);

深拷贝

function deepclone(obj) {
  // 先做类型判断
  if (typeof obj !== "object" || obj != null) {
    return obj;
  }

  // 数组? 对象?
  let result = obj instanceof Array ? [] : {};

  for (let key in obj) {
    if (obj.hasOwnPropetry(key)) {
      result[key] = deepclone(obj[key]);
    }
  }
  return result;
}

防抖

防抖是指事件被触发后,n秒内函数只能执行一次,如果在这n秒内又被触发,则重新计算执行时间。

function debounce(delay, fn) {
  let timer = 0;
  return function () {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, delay);
  };
}

节流

节流是指连续触发事件,但在n秒内只执行一次函数。

function throttle(delay, fn) {
  let timer = 0;
  return function () {
    if (timer) return;
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, delay);
  };
}

数组扁平化 只扁平一次

const arr = [1, 2, [1, [3, 4]], 4]; //  [1, 2, 1, [3,4], 4]

function flatten(arr) {
  let newArr = [];
  arr.forEach((c) => {
    newArr = newArr.concat(c);
  });
  console.info(11, newArr);
}

flatten(arr);

数组扁平化 彻底扁平化

const arr = [1, 2, [1, [3, 4]], 4]; //  [1, 2, 1, 3,4, 4]

function flatten(arr) {
  let result = [];

  arr.forEach((c) => {
    if (Array.isArray(c)) {
      const res = flatten(c);
      res.forEach((b) => result.push(b));
    } else {
      result.push(c);
    }
  });
  console.info(11, result);
  return result;
}

// function flatArrMany(arr){
//   let result = []

//   arr.forEach((item)=> {
//     if(Array.isArray(item)) {
//       result = result.concat(flatArrMany(item))
//     }else {
//       result.push(item)
//     }
//   })
//   console.info(4, result)
//   return result
// }

反转字符串

// reduce 方法
// const str = "kasdklka;dk;ka;sdj;lad";
// function reverseStr(str) {
//   if(!str) return str

//  let result = Array.from(str).reduce((pre, cur)=> {
//     return cur+pre
//   }, [])

//   return result;
// }

// 双指针
function reverseStr(str) {
  if (!str) return str;

  let result = Array.from(str);
  let i = 0;
  let j = result.length - 1;

  while (i < j) {
    temp = result[i];
    result[i] = result[j];
    result[j] = temp;
    i++;
    j--;
  }

  return result.join("");
}
const str = "kasdklka;dk;ka;sdj;lad";
console.info(reverseStr(str));

删除 排序数组的重复项

const arr = [1, 2, 3, 2, 1, 3, 2, 3, 2, 1, 3, 4, 5, 6];
function delRepeat(arr) {
  const obj = new Map();
  for (let i = 0; i < arr.length; i++) {
    const a = arr[i];
    if (!obj.get(a)) {
      obj.set(a, 1);
    } else {
      obj.set(a, obj.get(a) + 1);
    }
  }
  return arr.filter((c) => obj.get(c) === 1);
}
console.info(delRepeat(arr));


// const arr = [1, 2, 3, 2, 1, 3, 2, 3, 2, 1, 3, 4, 5, 6];
// let result = []
// arr.forEach(c=> {
//   if(!result.includes(c) && arr.filter(b=> b===c).length === 1) {
//      result.push(c)
//   }
// })
// console.info(444, result)
// }

实现 请使用自定义 React hook, 创建一个倒计时计时器,并且在每秒钟递减,直到达到 0 为止。并能提供驱动开始倒计时和结束到倒计时的能力。

import { useState, useEffect, useRef } from "react";
function useCountdown({ number = 60 }) {
  const [count, setCount] = useState(number);
  const timer = useRef(null);

  useEffect(() => {
    if (count <= 0) {
      clearInterval(timer.current);
      timer.current = null;
    }
    return () => clearInterval(timer.current);
  }, [count]);

  // 启动函数
  const start = () => {
    timer.current = setInterval(() => {
      setCount((preCount) => preCount - 1);
    }, 1000);
  };

  // 停止函数
  const stop = () => {
    if (timer.current) {
      clearInterval(timer.current);
      timer.current = null;
    }
  };

  return {
    start,
    stop,
    count,
  };
}

export default useCountdown;

实现上拉加载,下拉刷新

上拉刷新和下拉加载的原理主要依赖于监听滚动事件,并根据滚动位置触发相应的操作。以下是详细解释:

  1. 监听滚动事件:

    • 使用 scroll 事件监听器来捕捉用户的滚动行为。
    • 通过 scrollTop、scrollHeight 和 clientHeight 等属性来判断当前的滚动位置。
  2. 下拉刷新:

    • 当用户滚动到顶部(即 scrollTop === 0)时,触发刷新操作。
    • 在刷新操作中,可以调用一个函数(如 refresh)来获取最新的数据,并更新组件的状态。
    • 刷新完成后,重置刷新状态。
  3. 上拉加载:

    • 当用户滚动到底部(即 scrollTop + clientHeight >= scrollHeight - 5)时,触发加载更多操作。
    • 在加载更多操作中,可以调用一个函数(如 loadMore)来获取更多的数据,并追加到现有的数据列表中。
    • 加载完成后,重置加载状态。
  4. 状态管理:

    • 使用 useState 来管理当前的刷新状态和加载状态(如 isRefreshing 和 isFetching)。
    • 使用 useRef 来存储定时器或滚动容器的引用。
  5. 清理副作用:

    • 在组件卸载时,清除滚动事件监听器以避免内存泄漏。
import { useState, useEffect, useRef } from "react";

function useInfiniteScroll({ loadMore, refresh }) {
  const [isFetching, setIsFetching] = useState(false);
  const [isRefreshing, setIsRefreshing] = useState(false);
  const scrollRef = useRef(null);

  useEffect(() => {
    const handleScroll = () => {
      if (scrollRef.current) {
        const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;

        // 下拉刷新
        if (scrollTop === 0 && !isRefreshing) {
          setIsRefreshing(true);
          refresh().finally(() => setIsRefreshing(false));
        }

        // 上拉加载
        if (scrollTop + clientHeight >= scrollHeight - 5 && !isFetching) {
          setIsFetching(true);
          loadMore().finally(() => setIsFetching(false));
        }
      }
    };

    const scrollElement = scrollRef.current;
    if (scrollElement) {
      scrollElement.addEventListener("scroll", handleScroll);
    }

    return () => {
      if (scrollElement) {
        scrollElement.removeEventListener("scroll", handleScroll);
      }
    };
  }, [isFetching, isRefreshing, loadMore, refresh]);

  return { scrollRef, isFetching, isRefreshing };
}

export default useInfiniteScroll;

用法:

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

const App = () => {
  const [items, setItems] = useState(Array.from({ length: 20 }));

  const loadMore = async () => {
    // 模拟加载更多数据
    await new Promise((resolve) => setTimeout(resolve, 1000));
    setItems((prevItems) => [...prevItems, ...Array.from({ length: 20 })]);
  };

  const refresh = async () => {
    // 模拟刷新数据
    await new Promise((resolve) => setTimeout(resolve, 1000));
    setItems(Array.from({ length: 20 }));
  };

  const { scrollRef, isFetching, isRefreshing } = useInfiniteScroll({
    loadMore,
    refresh,
  });

  return (
    <div ref={scrollRef} style={{ height: "80vh", overflow: "auto" }}>
      {isRefreshing && <div>刷新中...</div>}
      <ul>
        {items.map((item, index) => (
          <li key={index}>Item {index + 1}</li>
        ))}
      </ul>
      {isFetching && <div>加载中...</div>}
    </div>
  );
};

export default App;

请使用一个函数实现一个 Map 数据结构的展平:

例如: map = {"a": {"b":"1", "d": "3"}, "c": "2"}
=>
{"a.b": "1", "a.d": "3", "c": "2"}

function flat(map) {
  let obj = {}
  for (const key in map) {
    if(typeof map[key] === 'object'){
      for (const key2 in map[key]) {
       obj[`${key}.${key2}`] = map[key][key2]
      }
    }
  }
  console.info(333, obj)
  return obj
}
flat({"a": {"b":"1", "d": "3"}, "c": "2"} )
function flat2(map) {
  let obj = {};
  for (const key in map) {
    if (typeof map[key] === "object") {
      const val = flat2(map[key]);
      for (const key2 in val) {
        obj[`${key}.${key2}`] = val[key2];
      }
    } else {
      obj[key] = map[key];
    }
  }
  console.info(8, obj);
  return obj;
}
flat2({ a: { b: "1", d: "3", d: { g: 3, t: 5, y: 8 } }, c: "2" });

假如有一个数组

const items =
[
	{"name": "张三", "age": "26", "gender": "M"},
	{"name": "李四", "age": "26", "gender": "F"},
	{"name": "王五", "age": "28", "gender": "F"},
	{"name": "赵六", "age": "28", "gender": "M"}
]
// 请提供一个函数,可以实现按照数据对象中的任何一个key分组:


function groupBy(items, keyFn) {
 return items.reduce((result, item)=> {
    const key = keyFn(item)
    if(!result[key]) {
      result[key] = []
    }
    result[key].push(item)
    return result
  },{})
}
const data = groupBy(items, (x)=> x.age)
console.info(333, data)


=>
{
	"26": [
		{"name": "张三", "age": "26", "gender": "M"},
		{"name": "李四", "age": "26", "gender": "F"},
	],
	"28": [
		{"name": "王五", "age": "28", "gender": "F"},
		{"name": "赵六", "age": "28", "gender": "M"}
	]

}

groupBy(items, (x)=> x.gender)
=>
{
	"M": [
		{"name": "张三", "age": "26", "gender": "M"},
		{"name": "赵六", "age": "28", "gender": "M"}
	],
	"F": [
		{"name": "李四", "age": "26", "gender": "F"},
		{"name": "赵六", "age": "28", "gender": "M"}
	]

}

将数组的 0 移动到末尾, 只能在原数组中操作

const arr = [1, 2, 0, 3, 0, 0, 3, 0, 2, 0];

function moveZero(array) {
  let zeroLength = 0;

  for (let index = 0; index < array.length - zeroLength; index++) {
    const n = array[index];
    if (n === 0) {
      array.splice(index, 1);
      array.push(0);
      index--;
      zeroLength++;
    }
  }
  return array;
}
console.info(33, moveZero(arr));

手写 promise ?

class MyPromise {
  constructor(executor) {
    this.state = "pending"; // 初始状态
    this.value = undefined; // 成功的值
    this.reason = undefined; // 失败的原因
    this.onFulfilledCallbacks = []; // 成功回调
    this.onRejectedCallbacks = []; // 失败回调

    const resolve = (value) => {
      if (this.state === "pending") {
        this.state = "fulfilled";
        this.value = value;
        this.onFulfilledCallbacks.forEach((fn) => fn());
      }
    };

    const reject = (reason) => {
      if (this.state === "pending") {
        this.state = "rejected";
        this.reason = reason;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

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

  then(onFulfilled, onRejected) {
    if (this.state === "fulfilled") {
      onFulfilled(this.value);
    } else if (this.state === "rejected") {
      onRejected(this.reason);
    } else if (this.state === "pending") {
      this.onFulfilledCallbacks.push(() => onFulfilled(this.value));
      this.onRejectedCallbacks.push(() => onRejected(this.reason));
    }
  }
}

数组转成树型结构

const list = [   
    { id: 1, name: 'Node 1', parentId: null },   
    { id: 2, name: 'Node 1.1', parentId: 1 },   
    { id: 3, name: 'Node 1.2', parentId: 1 },   
    { id: 4, name: 'Node 2', parentId: null },   
    { id: 5, name: 'Node 2.1', parentId: 4 },   
    { id: 6, name: 'Node 2.2', parentId: 4 },   
    { id: 7, name: 'Node 2.1.1', parentId: 5 },   
];   

function arrToTree(items) {
  let rootItems = [];
  let lookup = {};

  for (let item of items) {
    const id = item.id;
    const parentId = item.parentId;

    // 打印当前 item 的 id 和 parentId
    console.log("当前 item:", item);
    // debugger;

    if (!lookup[id]) {
      lookup[id] = { ...item, children: [] };
      // 打印新建的 lookup 项
      console.log("创建 lookup[id]:", lookup[id]);
      // debugger;
    }

    if (parentId == null) {
      rootItems.push(lookup[id]);
      // 打印 rootItems 的变化
      console.log("添加到 rootItems:", rootItems);
      // debugger;
    } else {
      // if (!lookup[parentId]) {
      //   lookup[parentId] = { children: [] };
      //   // 打印新建的父节点项
      //   console.log("创建父节点 lookup[parentId]:", lookup[parentId]);
      //   debugger;
      // }
      lookup[parentId].children.push(lookup[id]);
      // 打印父节点的 children 更新
      console.log("更新父节点的 children:", lookup[parentId].children);
      // debugger;
    }

    // 打印每次循环后的 lookup
    console.log("当前 lookup 状态:", lookup);
    // debugger;
  }

  return rootItems;
}

console.log("最终树形结构:", arrToTree(list));

怎么实现一个 事件监听器, 类似于 vuex或者redux 等?

class EventEmitter {
  constructor() {
    this.events = {};
  }
  // 订阅事件
  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
  }
  // 发布事件
  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach(listener => listener(...args));
    }
  }

  // 取消订阅
  off(event, listener) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(l => l !== listener);
    }
  }
}

// 示例
const eventEmitter = new EventEmitter();

function logEvent(data) {
  console.log('Event received:', data);
}

// 订阅事件
eventEmitter.on('testEvent', logEvent);

// 发布事件
eventEmitter.emit('testEvent', { some: 'data' });  // 输出: Event received: { some: 'data' }

// 取消订阅
eventEmitter.off('testEvent', logEvent);

// 再次发布事件(无输出,因为已取消订阅)
eventEmitter.emit('testEvent', { some: 'data' });

后端一次性返回 10w调试,前端如何渲染

  1. 前端分页渲染(初级)
  2. 定时器分批次渲染(中级)
  3. 使用 requestAnimationFrame(callback) 代替定时器渲染, 请求动画贞的方式(中级)
  4. 文档碎片方式:document.createDocumentFragment()

编写一个函数,输入int,返回整数逆序后的字符串,如输入1234, 返回子字符串4321

  1. 必须使用递归函数
  2. 不能使用全局变量
  3. 输入函数必须只有一个参数
  4. 必须返回字符串
const num = 1234
function setNum(num) {
  if(num < 10) return num
  const first = num % 10 
  const last =  Math.floor(num / 10)
  return String(first) + setNum(last)
}
console.info(333, setNum(num))

并发

function createAsyncTask(delay, index) {
  return () => new Promise((resolve) => {
    console.log(`Task event is ${index}, ${delay}s`);
    return setTimeout(() => resolve(), delay * 1000)
  })
}

const tasks = [1,6,2,4,2,6,3,7,3].map((t,i)=> createAsyncTask(t,i))

async function runTask(taskList, limit = 3) {
  let startTime = Date.now()
  let activeRuns = []

  for (let i = 0; i < taskList.length; i++) {
    const element = taskList[i];
    
    const foo = element().then(res=> {
      activeRuns = activeRuns.filter(c=> c!== foo)
      return res
    })
    activeRuns.push(foo)
    if(activeRuns.length >= limit) {
      await Promise.race(activeRuns)
    }
  }
  await Promise.all(activeRuns)

  return  Date.now() - startTime
}

runTask(tasks).then(res=> console.info('共花了多少时间', res))
Last Updated:
Contributors: zhanghusheng