Skip to content
On this page

面试

🕒 Published at:

参考:f.zuo11.com推荐的js面试题
改内容由AI提供,可能会有错误。

1. 实现promise.all()

js
Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let result = [];
    let count = 0;
    for (let i = 0; i < promises.length; i++) {
      promises[i].then((data) => {
        result[i] = data;
        if (++count === promises.length) {
          resolve(result);
        }
      }, reject);
    }
  });
};

//如何运行这个promise.all()?

js
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p1");
  }, 1000);
});
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p2");
  }, 2000);
});
let p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("p3");
  }, 3000);
});
Promise.all([p1, p2, p3]).then((data) => {
  console.log(data);
});
//输出结果:["p1", "p2", "p3"]

这个promise.all()例子有什么用呢? 假设我们有一个页面,需要请求多个接口,然后渲染页面,这时候就可以用promise.all()来实现。
可以举例子,需要请求什么接口么?

  1. 请求用户信息
  2. 请求用户的订单信息
  3. 请求用户的地址信息
  4. 请求用户的优惠券信息
    可以看到,这四个接口是没有依赖关系的,可以同时请求,这时候就可以用promise.all()来实现。
    实现这个1. 请求用户信息 2. 请求用户的订单信息 3. 请求用户的地址信息 4. 请求用户的优惠券信息请求promise.all()
js
// 定义请求函数
function fetchUserInfo() {
  // 返回一个 Promise,模拟异步请求用户信息接口
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const userInfo = { name: 'Alice', age: 25 };
      resolve(userInfo);
    }, 2000);
  });
}

function fetchUserOrders() {
  // 返回一个 Promise,模拟异步请求用户订单信息接口
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const orders = ['Order 1', 'Order 2', 'Order 3'];
      resolve(orders);
    }, 3000);
  });
}

function fetchUserAddresses() {
  // 返回一个 Promise,模拟异步请求用户地址信息接口
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const addresses = ['Address 1', 'Address 2', 'Address 3'];
      resolve(addresses);
    }, 2500);
  });
}

function fetchUserCoupons() {
  // 返回一个 Promise,模拟异步请求用户优惠券信息接口
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const coupons = ['Coupon 1', 'Coupon 2', 'Coupon 3'];
      resolve(coupons);
    }, 1500);
  });
}

// 使用 Promise.all() 请求多个接口
Promise.all([
  fetchUserInfo(),
  fetchUserOrders(),
  fetchUserAddresses(),
  fetchUserCoupons()
])
  .then(([userInfo, userOrders, userAddresses, userCoupons]) => {
    // 在这里进行页面渲染逻辑,将接口返回的数据渲染到页面上
    console.log('用户信息:', userInfo);
    console.log('用户订单:', userOrders);
    console.log('用户地址:', userAddresses);
    console.log('用户优惠券:', userCoupons);
  })
  .catch(error => {
    // 处理错误情况
    console.error('请求接口发生错误:', error);
  });
  //输出结果
//用户信息: {name: "Alice", age: 25}
//用户订单: (3) ["Order 1", "Order 2", "Order 3"]
//用户地址: (3) ["Address 1", "Address 2", "Address 3"] 
//用户优惠券: (3) ["Coupon 1", "Coupon 2", "Coupon 3"]
//这样就实现了多个接口同时请求,然后渲染页面。

promise.all()的实现原理

promise.all()的实现原理就是,将多个promise对象放到一个数组中,然后遍历这个数组,将每个promise对象的结果放到一个数组中,然后返回这个数组。

2. Array.prototype.flat 数组扁平化

Array.prototype.flat()方法会按照一个可指定的深度递归遍历数组,将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

js
var arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]

写一个原生的Array.prototype.flat

js
Array.prototype.flat = function (depth = 1) {
  let result = [];
  this.forEach((item) => {
    if (Array.isArray(item) && depth > 0) {
      result.push(...item.flat(depth - 1));
    } else {
      result.push(item);
    }
  });
  return result;
};

3. instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

js
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);
console.log(auto instanceof Car);
// expected output: true

instanceof的实现原理

js
function myInstanceof(left, right) {
  let prototype = right.prototype;
  left = left.__proto__;
  while (true) {
    if (left === null || left === undefined) {
      return false;
    }
    if (prototype === left) {
      return true;
    }
    left = left.__proto__;
  }
}

交通灯

js
function red() {
  console.log("red");
}
function green() {
  console.log("green");
}
function yellow() {
  console.log("yellow");
}
let light = function (timer, cb) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      cb();
      resolve();
    }, timer);
  });
};
let step = function () {
  Promise.resolve()
    .then(() => {
      return light(3000, red);
    })
    .then(() => {
      return light(2000, green);
    })
    .then(() => {
      return light(1000, yellow);
    })
    .then(() => {
      step();
    });
};
step();

如何停止上面的promise

js
let shouldStop = false;

function red() {
  console.log("red");
}

function green() {
  console.log("green");
}

function yellow() {
  console.log("yellow");
}

let light = function (timer, cb) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      cb();
      resolve();
    }, timer);
  });
};

let step = function () {
  if (shouldStop) {
    return;
  }

  Promise.resolve()
    .then(() => {
      return light(3000, red);
    })
    .then(() => {
      return light(2000, green);
    })
    .then(() => {
      return light(1000, yellow);
    })
    .then(() => {
      step();
    });
};

// 停止函数的执行
function stop() {
  shouldStop = true;
}

// 启动函数的执行
function start() {
  shouldStop = false;
  step();
}

start(); // 启动函数的执行

// 运行一段时间后调用 stop() 函数停止执行
setTimeout(stop, 10000);

4. 数组去重

js
let arr = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
let newArr = [...new Set(arr)];
console.log(newArr); // [1, 2, 3, 4, 5]

6. 数组乱序

js
let arr = [1, 2, 3, 4, 5];
let newArr = arr.sort(() => Math.random() - 0.5);
console.log(newArr); // [3, 1, 5, 2, 4]

7. 数组最大值

js
let arr = [1, 2, 3, 4, 5];
let max = Math.max(...arr);
console.log(max); // 5

8. 封装异步的fetch,使用async await方式来使用

js
function fetch(url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.responseText);
        } else {
          reject(xhr.responseText);
        }
      }
    };
    xhr.send(null);
  });
}
async function getData() {
  let data = await fetch("https://janeanne218.github.io/myblog2/");
  console.log(data);
}
getData();

9. 封装一个工具函数输入一个promiseA返回一个promiseB如果超过1s没返回则抛出异常如果正常则输出正确的值

js
//封装一个工具函数输入一个promiseA返回一个promiseB如果超过1s没返回则抛出异常如果正常则输出正确的值
function timeout(promise) {
  return new Promise((resolve, reject) => {
    let timer = setTimeout(() => {
      reject("超时");
    }, 1000);
    promise.then((res) => {
      clearTimeout(timer);
      resolve(res);
    });
  });
}
let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("成功");
  }, 500);
});
timeout(promise).then((res) => {
  console.log(res);
});

10. 使用 Promise 封装 AJAX 请求

js
function ajax(url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.responseText);
        } else {
          reject(xhr.responseText);
        }
      }
    };
    xhr.send(null);
  });
}
ajax("https://janeanne218.github.io/myblog2/").then((res) => {
  console.log(res);
});

我们能反过来使用 setinterval 模拟实现 settimeout 吗?

js
function mySetTimeout(fn, time) {
  let timer = setInterval(() => {
    fn();
    clearInterval(timer);
  }, time);
}
mySetTimeout(() => {
  console.log("hello");
}, 1000);

异步任务:依次发送 3 次网络请求,拿到服务器数据

js
function fetch(url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.responseText);
        } else {
          reject(xhr.responseText);
        }
      }
    };
    xhr.send(null);
  });
}
let urls = [
  "https://janeanne218.github.io/myblog2/",
  "https://janeanne218.github.io/myblog2/",
  "https://janeanne218.github.io/myblog2/",
];
let promises = urls.map((url) => fetch(url));
Promise.all(promises).then((res) => {
  console.log(res);
});

实现网络请求超时判断,超过三秒视为超时

js
function fetch(url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.responseText);
        } else {
          reject(xhr.responseText);
        }
      }
    };
    xhr.send(null);
  });
}
function timeout(promise) {
  return new Promise((resolve, reject) => {
    let timer = setTimeout(() => {
      reject("超时");
    }, 3000);
    promise.then((res) => {
      clearTimeout(timer);
      resolve(res);
    });
  });
}
timeout(fetch("https://janeanne218.github.io/myblog2/"))
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });

实现一个函数,可以对 url 中的 query 部分做拆解,返回一个 key - value 形式的 object

js
function parseQuery(url) {
  let obj = {};
  let query = url.split("?")[1];
  let queryArr = query.split("&");
  queryArr.forEach((item) => {
    let key = item.split("=")[0];
    let value = item.split("=")[1];
    obj[key] = value;
  });
  return obj;
}
let url = "https://janeanne218.github.io/myblog2/?name=anne&age=18";
console.log(parseQuery(url)); // {name: "anne", age: "18"}

settimeout系统补偿时间

js
setTimeout(() => {
  console.log(1);
}, 0);
new Promise((resolve, reject) => {
  console.log(2);
  resolve();
}).then(() => {
  console.log(3);
});
console.log(4);
// 2 4 3 1

为什么是2 4 3 1的输出顺序? 因为setTimeout(fn, 0)并不是立即执行fn,而是将fn放入宏任务队列中,等待执行,而Promise.then(fn)是将fn放入微任务队列中,等待执行,所以先执行2,再执行4,再执行3,最后执行1。 这里宏任务队列,和微任务对立的区别
宏任务队列:setTimeout、setInterval、setImmediate、I/O、UI rendering
微任务队列:process.nextTick、Promise.then、Object.observe、MutationObserver
什么是宏任务队列?什么是微任务队列?
宏任务队列:宏任务队列是一个存放宏任务的队列,宏任务的执行由主线程来执行,宏任务队列可以有多个,排在前面的先执行,后面的后执行。
微任务队列:微任务队列是一个存放微任务的队列,微任务的执行由js引擎来执行,微任务队列只有一个,排在前面的先执行,后面的后执行。
什么是系统补偿时间?系统补偿时间与宏任务和微任务有什么关系?
系统补偿时间:系统补偿时间是指js引擎为了保证宏任务和微任务的执行顺序而设置的一个时间,这个时间一般是4ms,也就是说,如果在4ms内,宏任务执行完毕,那么就会立即执行微任务,如果4ms内宏任务没有执行完毕,那么就会等待宏任务执行完毕后立即执行微任务。

setTimeout准时

js
function setTimeoutWithOffset(fn, interval, ...args) {
  let startTime = Date.now()
  let count = 0// 执行次数
  let timer = null
  const task = () => {
    // offset = timeNow - (startTime + count * interval)
    const offset = Date.now() - (startTime + count * interval)
    console.log(`${count + 1}次 系统延迟时间${offset}`)//
    timer = setTimeout(() => {
      fn.apply(this, args);
      count++
      task();
    }, interval - offset);
  };
  task();
  return () => clearTimeout(timer);
}

//TEST
const stop = setTimeoutWithOffset(() => console.log(123), 1000)
setTimeout(() => {
  stop()
}, 5000);

请求五秒未完成则终止

js
//请求五秒未完成则终止
function fetch(url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.responseText);
        } else {
          reject(xhr.responseText);
        }
      }
    };
    xhr.send(null);
  });
}
function timeout(promise, interval) {
  return new Promise((resolve, reject) => {
    let timer = setTimeout(() => {
      reject("超时");
    }, interval);
    promise.then((res) => {
      clearTimeout(timer);
      resolve(res);
    });
  });
}
timeout(fetch("https://janeanne218.github.io/myblog2/"), 5000)
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });

JS 异步数据流,实现并发异步请求,结果顺序输出

js
function fetch(url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.responseText);
        } else {
          reject(xhr.responseText);
        }
      }
    };
    xhr.send(null);
  });
}
function fetchAll(urls) {
  let arr = [];
  urls.forEach((url) => {
    arr.push(fetch(url));
  });
  return Promise.all(arr);
}
fetchAll([
  "https://janeanne218.github.io/myblog2/",
  "https://janeanne218.github.io/myblog2/",
  "https://janeanne218.github.io/myblog2/",
])
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });

什么是异步数据流?什么是并发异步请求?
异步数据流:异步数据流是指多个异步请求的数据流,异步数据流的数据是按照请求的顺序来的,比如:请求1,请求2,请求3,那么数据流的数据就是请求1的数据,请求2的数据,请求3的数据。
并发异步请求:并发异步请求是指多个异步请求同时发出,不需要等待上一个请求的结果,比如:请求1,请求2,请求3,那么请求1,请求2,请求3同时发出,不需要等待上一个请求的结果。
异步数据流和并发异步请求的应用场景?
异步数据流:异步数据流的应用场景是在需要按照顺序来获取数据的时候,比如:需要获取用户的信息,需要获取用户的订单信息,需要获取用户的地址信息,这三个请求的数据是按照顺序来的,这个时候就可以使用异步数据流。
并发异步请求:并发异步请求的应用场景是在需要同时获取多个数据的时候,比如:需要获取用户的信息,需要获取用户的订单信息,需要获取用户的地址信息,这三个请求的数据是不需要按照顺序来的,这个时候就可以使用并发异步请求。

Promise 串行

js
function fetch(url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.responseText);
        } else {
          reject(xhr.responseText);
        }
      }
    };
    xhr.send(null);
  });
}
function fetchAll(urls) {
  let arr = [];
  urls.forEach((url) => {
    arr.push(fetch(url));
  });
  return Promise.all(arr);
}
fetchAll([
  "https://janeanne218.github.io/myblog2/",
  "https://janeanne218.github.io/myblog2/",
  "https://janeanne218.github.io/myblog2/",
])
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });

Promise 并行

js
function fetch(url) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.responseText);
        } else {
          reject(xhr.responseText);
        }
      }
    };
    xhr.send(null);
  });
}
function fetchAll(urls) {
  let arr = [];
  urls.forEach((url) => {
    arr.push(fetch(url));
  });
  return Promise.all(arr);
}
fetchAll([
  "https://janeanne218.github.io/myblog2/",
  "https://janeanne218.github.io/myblog2/",
  "https://janeanne218.github.io/myblog2/",
])
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });

promise的串行原理以及并行原理
promise的串行原理:promise的串行原理是通过promise的then方法来实现的,比如:promise1.then(res => {return promise2}).then(res => {return promise3}),这样就实现了promise的串行。
promise的并行原理:promise的并行原理是通过promise的all方法来实现的,比如:Promise.all([promise1, promise2, promise3]),这样就实现了promise的并行。
promise的串行和并行的区别?
串行和异步数据流是一样的,都是按照顺序来获取数据,但是串行是通过promise的then方法来实现的,异步数据流是通过promise的all方法来实现的。

处理高并发, 100 条数据,带宽为 10, 跑满带宽

js
//应用场景:批量上传文件
function asyncPool(limit,dates,fn) {
  let count = 0;// 计数器
  const exec = [];// 正在执行的任务
  const values = [];// 执行完成的任务
  const _asyncPool = () => {// 递归执行
    if (count === dates.length) { // 执行完毕
      return Promise.resolve();// 返回一个成功的promise
    }

    const item = dates[count++];// 取出第一个任务
    const p = Promise.resolve().then(()=>fn(item));// 执行任务
    values.push(p);// 保存任务的执行结果

    const _p = p.then(()=>exec.splice(exec.indexOf(_p), 1));// 执行完毕,从正在执行的任务中移除
    exec.push(_p);// 保存正在执行的任务
    
    let next = Promise.resolve();// 下一个任务
    if (exec.length >= limit) { // 达到最大并发数
      next = Promise.race(exec);// 等待最快的任务执行完毕
    }
    return next.then(()=>_asyncPool());// 递归执行
  }
  return _asyncPool().then(()=>Promise.all(values));// 返回所有任务的执行结果

}

设计一个简单的任务队列, 要求分别在 1,3,4 秒后打印出 "1", "2", "3";

js
class Queue {
  constructor() {
    this.queue = [];
  }
  task(time, fn) {
    this.queue.push({ time, fn });
    return this;
  }
  start() {
    this.queue.forEach((item) => {
      setTimeout(() => {
        item.fn();
      }, item.time);
    });
  }
}
new Queue()
  .task(1000, () => {
    console.log(1);
  })
  .task(3000, () => {
    console.log(2);
  })
  .task(4000, () => {
    console.log(3);
  })
  .start();

请实现一个批量请求函数 multiRequest(urls, maxNum),要求如下:

• 要求最大并发数 maxNum • 每当有一个请求返回,就留下一个空位,可以增加新的请求 • 所有请求完成后,结果按照 urls 里面的顺序依次打出

js
function multiRequest(urls = [], maxNum) {
  const len = urls.length;
  const result = new Array(len).fill(false);
  let count = 0;
  return new Promise((resolve, reject) => {
    while (count < maxNum) {
      next();
    }
    function next() {
      let current = count++;
      if (current >= len) {
        !result.includes(false) && resolve(result);
        return;
      }
      const url = urls[current];
      console.log(`开始 ${current}`, new Date().toLocaleString());
      fetch(url)
        .then((res) => {
          console.log(`完成 ${current}`, new Date().toLocaleString());
          result[current] = res;
          if (current < len) {
            next();
          }
        })
        .catch((err) => {
          console.log(`结束 ${current}`, new Date().toLocaleString());
          result[current] = err;
          if (current < len) {
            next();
          }
        });
    }
  });
}

实现有并行限制的 Promise 调度器

js
//原理:通过一个计数器来控制并发数量,每次并发执行完毕后,再继续执行下一个任务,直到任务全部执行完毕

class Scheduler {
  constructor() {// 任务队列
    this.queue = [];// 最大并发数
    this.maxCount = 2;// 当前并发数
    this.runCounts = 0;// 等待队列
  }
  add(promiseCreator) {
    this.queue.push(promiseCreator);// 执行任务
  }
  taskStart() {
    for (let i = 0; i < this.maxCount; i++) {
      this.request();// 先执行最大并发数个任务
    }
  }
  request() {// 没有任务了或者超出最大并发数,直接return
    if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
      return;
    }
    this.runCounts++;// 取出一个任务执行
    this.queue
      .shift()()
      .then(() => {
        this.runCounts--;
        this.request();
      });
  }
}
// 测试代码
const timeout = (time) =>
  new Promise((resolve) => {
    setTimeout(resolve, time);
  });
const scheduler = new Scheduler();
const addTask = (time, order) => {
  scheduler.add(() => timeout(time).then(() => console.log(order)));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();// output: 2 3 1 4

使用 Promise 改写回调地狱

回调地狱:多个异步操作依次执行,后一个异步操作依赖前一个异步操作的结果,如果前一个异步操作失败,后一个异步操作就不执行了

js
//原理:通过promise的then方法来实现
function ajax(url) {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.responseType = "json";
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    xhr.send();
  });
}
ajax("/api/users.json")
  .then((res) => {
    console.log(res);
    return ajax("/api/urls.json");
  })
  .then((res) => {
    console.log(res);
  });

设计一个函数,该函数的参数为可同时发送请求的大小,返回一个函数,该函数的参数为要请求的 url。 实现的效果为,同时发送 n 个请求,当有请求返回后往请求队列里 push 新的请求,并输出刚刚结束的请求的返回值

js
function createRequestQueue(concurrency) {
  let count = 0; // 当前正在执行的请求数量
  const requestQueue = []; // 请求队列

  async function makeRequest(url) {
    try {
      const response = await fetch(url); // 发送请求
      const result = await response.json(); // 解析响应数据
      console.log("Response:", result); // 输出刚刚结束的请求的返回值
    } catch (error) {
      console.error("Error:", error);
    } finally {
      count--; // 请求完成,减少计数器
      if (requestQueue.length > 0) {
        // 如果还有待发送的请求,则将新的请求加入请求队列
        const nextRequest = requestQueue.shift();
        makeRequest(nextRequest);
      }
    }
  }

  return function sendRequest(url) {
    if (count < concurrency) {
      // 如果正在执行的请求数量小于并发数,则立即发送请求
      count++;
      makeRequest(url);
    } else {
      // 否则将请求加入请求队列
      requestQueue.push(url);
    }
  };
}

// 示例用法
const sendRequest = createRequestQueue(3); // 设置并发数为3
sendRequest("https://example.com/url1");
sendRequest("https://example.com/url2");
sendRequest("https://example.com/url3");
sendRequest("https://example.com/url4");
// ...

Promise.retry 超时重新请求,并在重试一定次数依然失败时输出缓存内容

js
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function retry(fn, maxRetries, timeout) {
  let retries = 0;
  let cache;

  while (retries < maxRetries) {
    try {
      const result = await Promise.race([
        fn(),
        delay(timeout).then(() => { throw new Error('Timeout'); })
      ]);

      // 请求成功,返回结果
      return result;
    } catch (error) {
      console.error(`Attempt #${retries + 1} failed: ${error.message}`);
      retries++;

      // 如果是超时错误,则继续重试
      if (error.message === 'Timeout') {
        continue;
      }

      // 缓存请求结果
      cache = error;
    }
  }

  // 达到最大重试次数后,输出缓存内容
  console.log('Max retries reached, returning cache:', cache);
  return cache;
}

// 示例用法
const fetchData = () => {
  // 模拟网络请求,这里使用一个异步的 Promise 来模拟
  return new Promise((resolve, reject) => {
    const random = Math.random();
    setTimeout(() => {
      if (random < 0.7) {
        resolve('Data fetched successfully');
      } else {
        reject('Failed to fetch data');
      }
    }, 1000);
  });
};

retry(fetchData, 3, 2000)
  .then(result => console.log('Final result:', result))
  .catch(error => console.error('All retries failed:', error));

写一个 mySetInterVal(fn, a, b),每次间隔 a,a+b,a+2b 的时间,然后写一个 myClear,停止上面的 mySetInterVal

js
function mySetInterVal(fn, a, b) {
  let timer = null;
  let count = 0;
  function interval() {
    timer = setTimeout(() => {
      fn();
      count++;
      interval();
    }, a + b * count);
  }
  interval();
  return {
    clear: function () {
      clearTimeout(timer);
    },
  };
}

产生一个不重复的随机数组

js
function randomArray(n, min, max) {
  const arr = [];
  for (let i = 0; i < n; i++) {
    const random = Math.floor(Math.random() * (max - min + 1)) + min;
    if (arr.indexOf(random) === -1) {
      arr.push(random);
    } else {
      i--;
    }
  }
  return arr;
}

await async 如何实现

js
function asyncToGenerator(generatorFunc) {
  // 返回的是一个新的函数
  return function () {
    // 先调用generator函数 生成迭代器
    // 对应 var gen = testG()
    const gen = generatorFunc.apply(this, arguments);

    // 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
    // var test = asyncToGenerator(testG)
    // test().then(res => console.log(res))
    return new Promise((resolve, reject) => {
      // 内部定义一个step函数 用来一步一步的跨过yield的阻碍
      // key有next和throw两种取值,分别对应了gen的next和throw方法
      // arg参数则是用来把promise resolve出来的值交给下一个yield
      function step(key, arg) {
        let generatorResult;
        // 这个方法需要包裹在try catch中
        // 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
        try {
          generatorResult = gen[key](arg);
        } catch (error) {
          return reject(error);
        }
        // gen.next() 得到的结果是一个 { value, done } 的结构
        const { value, done } = generatorResult;
        if (done) {
          // 如果已经完成了 就直接resolve这个promise
          // 这个done是在最后一次调用next后才会为true
          // 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
          // 这个value也就是generator函数最后的返回值
          return resolve(value);
        } else {
          // 除了最后结束的时候外,每次调用gen.next()
          // 其实是返回 { value: Promise, done: false } 的结构,
          // 这里要注意的是Promise.resolve可以接受一个promise为参数
          // 并且这个promise参数被resolve的时候,这个then才会被调用
          return Promise.resolve(
            // 这个value对应的是yield后面的promise
            value
          ).then(
            // value这个promise被resove的时候,就会执行next
            // 并且只要done不是true的时候 就会递归的往下解开promise
            // 对应gen.next().value.then(value => {
            //    gen.next(value).value.then(value2 => {
            //       gen.next()
            //
            //      // 此时done为true了 整个promise被resolve了
            //      // 最外部的test().then(res => console.log(res))的then就开始执行了
            //    })
            // })
            function onResolve(val) {
              step("next", val);
            },
            // 如果promise被reject了 就再次进入step函数
            // 不同的是,这次的try catch中调用的是gen.throw(err)
            // 那么自然就被catch到 然后把promise给reject掉啦
            function onReject(err) {
              step("throw", err);
            }
          );
        }
      }
      step("next");
    });
  };
}

计算ul下有多少个li

js
const ulElement = document.querySelector('#readme > div.Box-body.px-5.pb-5 > article > ul:nth-child(35)');
const liElements = ulElement.querySelectorAll('li');
console.log('Number of li elements:', liElements.length);

// 假设 `nodeList` 是一个 NodeList 对象
const nodeList = document.querySelectorAll('#readme > div.Box-body.px-5.pb-5 > article > ul:nth-child(35) li');

// 使用 Array.from() 将 NodeList 转换为数组,并使用 map() 方法获取每个元素的 innerText
const innerTextArray = Array.from(nodeList).map((element) => element.innerText);

console.log(innerTextArray);

实现圆形环状进度条

html
<!DOCTYPE html>
<html>
<head>
  <style>
    .progress-ring {
      width: 100px;
      height: 100px;
      background-color: lightgray;
      border-radius: 50%;
      position: relative;
      overflow: hidden;
    }
    
    .progress-ring .circle {
      width: 100%;
      height: 100%;
      position: absolute;
      transform: rotate(-90deg);
    }
    
    .progress-ring .mask {
      width: 100%;
      height: 100%;
      clip: rect(0, 50px, 100px, 0);
      background-color: blue;
      position: absolute;
    }
  </style>
</head>
<body>
  <div class="progress-ring">
    <div class="circle">
      <div class="mask"></div>
    </div>
  </div>

  <script>
    function setProgress(percent) {
      const mask = document.querySelector('.mask');
      const circle = document.querySelector('.circle');
      const radius = circle.clientWidth / 2;
      const circumference = 2 * Math.PI * radius;

      const offset = circumference - percent / 100 * circumference;
      mask.style.strokeDasharray = `${circumference} ${circumference}`;
      mask.style.strokeDashoffset = offset;
    }

    // 调用 setProgress 函数设置进度百分比
    setProgress(75);
  </script>
</body>
</html>

1px问题

1 px问题"是指在高分辨率屏幕上显示像素精细度为1像素的细线或细节时,由于屏幕像素密度的限制,可能导致这些线或细节显示不清晰或模糊的情况。这种情况通常发生在使用低分辨率图像或设计进行显示的情况下,在高分辨率屏幕上细微的细节可能会失去清晰度,呈现出锯齿状或不平滑的外观。

在传统屏幕上,每个像素对应一个物理像素点,因此可以轻松显示1像素的精细线条。但是,随着高分辨率(如Retina显示器)的出现,设备的像素密度增加,将更多的物理像素放入相同的空间中,从而使1像素的细线条变得难以清晰地显示。

为了解决1 px问题,可以采用一些技术手段,例如:

使用矢量图形:使用矢量图形格式(如SVG),它们可以无损缩放而不会受到像素密度影响。 使用CSS的transform: scale()属性:通过缩放元素来实现清晰的细线条效果。 使用像素比媒体查询:根据设备的像素比(如@media (-webkit-min-device-pixel-ratio: 2))应用不同的样式或图像资源,以适应高分辨率屏幕。 这些方法可以提供更好的显示效果,并解决在高分辨率屏幕上出现的1 px问题。

TS

mypick

设计模式相关

单例模式

单例模式原理:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 单例模式应用场景:全局缓存、全局状态管理、浏览器中的 window 对象、登录框、购物车、vuex、redux、react-router、vue-router 等。 我要如何才算学会了单例模式呢?

js
// 1. 使用命名空间
var namespace1 = {
  a: function () {
    console.log(1)
  },
  b: function () {
    console.log(2)
  }
}
// 2. 使用闭包封装私有变量
var user = (function () {
  var _name = 'sven',
    _age = 29
  return {
    getUserInfo: function () {
      return _name + '-' + _age
    }
  }
})()
// 3. 使用 ES6 的 class
class SingleObject {
  login() {
    console.log('login...')
  }
}
SingleObject.getInstance = (function () {
  let instance
  return function () {
    if (!instance) {
      instance = new SingleObject()
    }
    return instance
  }
})()
let obj1 = SingleObject.getInstance()
obj1.login()
let obj2 = SingleObject.getInstance()
obj2.login()
console.log('obj1 === obj2', obj1 === obj2)
// 4. 使用代理模式
var CreateDiv = function (html) {
  this.html = html
  this.init()
}
CreateDiv.prototype.init = function () {
  var div = document.createElement('div')
  div.innerHTML = this.html
  document.body.appendChild(div)
}
var ProxySingletonCreateDiv = (function () {
  var instance
  return function (html) {
    if (!instance) {
      instance = new CreateDiv(html)
    }
    return instance
  }
})()
var a = new ProxySingletonCreateDiv('sven1')
var b = new ProxySingletonCreateDiv('sven2')
console.log(a === b)

组件

全选

歌词滚动

js
const lyrics = ``
const lyricsList = lyrics.split('\n').filter(line => line.trim() !== '');
const ul = document.createElement('ul');

for (let i = 6; i < lyricsList.length; i++) {
  const line = lyricsList[i];
  const [time, text] = line.split(']');
  const formattedTime = time.slice(1);
  const li = document.createElement('li');
  li.textContent = text.trim();
  li.setAttribute('data-time', formattedTime);
  ul.appendChild(li);
}

// Add the ul element to the DOM
document.body.appendChild(ul);