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

<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 类型。 • 共同特点
- title icon 属性
- isDisabled 方法(可直接返回 false)
- exeC 方法,执行菜单的逻辑
• 不同点
- button 类型,执行 exec 时打印'hello'
- select 类型,执行 exec 时返回一个数组 ['item', 'item2', 'item3']
- 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
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];

快速排序 (去重)
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;
实现上拉加载,下拉刷新
上拉刷新和下拉加载的原理主要依赖于监听滚动事件,并根据滚动位置触发相应的操作。以下是详细解释:
监听滚动事件:
- 使用
scroll事件监听器来捕捉用户的滚动行为。 - 通过
scrollTop、scrollHeight和clientHeight等属性来判断当前的滚动位置。
- 使用
下拉刷新:
- 当用户滚动到顶部(即
scrollTop === 0)时,触发刷新操作。 - 在刷新操作中,可以调用一个函数(如
refresh)来获取最新的数据,并更新组件的状态。 - 刷新完成后,重置刷新状态。
- 当用户滚动到顶部(即
上拉加载:
- 当用户滚动到底部(即
scrollTop + clientHeight >= scrollHeight - 5)时,触发加载更多操作。 - 在加载更多操作中,可以调用一个函数(如
loadMore)来获取更多的数据,并追加到现有的数据列表中。 - 加载完成后,重置加载状态。
- 当用户滚动到底部(即
状态管理:
- 使用
useState来管理当前的刷新状态和加载状态(如isRefreshing和isFetching)。 - 使用
useRef来存储定时器或滚动容器的引用。
- 使用
清理副作用:
- 在组件卸载时,清除滚动事件监听器以避免内存泄漏。
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调试,前端如何渲染
- 前端分页渲染(初级)
- 定时器分批次渲染(中级)
- 使用 requestAnimationFrame(callback) 代替定时器渲染, 请求动画贞的方式(中级)
- 文档碎片方式:document.createDocumentFragment()
编写一个函数,输入int,返回整数逆序后的字符串,如输入1234, 返回子字符串4321
- 必须使用递归函数
- 不能使用全局变量
- 输入函数必须只有一个参数
- 必须返回字符串
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))
