# Js基础总结

# 1. 去除字符串两边空格

const trim = (str, type) => {
  const regObj = {
    left: /^\s+/,
    right: /\s+$/,
    both: /(^\s+)|(\s+$)/g,
    all: /\s+/g,
  };
  const reg = (type && regObj[type]) ?? regObj.both;
  return str.replace(reg, '');
};
1
2
3
4
5
6
7
8
9
10

# 2. 去除字符串中最后一个指定的字符

const delLast = (str, target) => {
  const reg = new RegExp(`${target}(?=([^${target}]*)$)`);
  return str.replace(reg, '');
};
const delLast1 = (str, target) => {
  return str.split('').reverse().join('').replace(target, '')
    .split('')
    .reverse()
    .join('');
};
const delLast2 = (str, target) => {
  const index = str.lastIndexOf(target);
  return str.substring(0, index) + str.substring(index + 1, str.length);
};
console.log(delLast('Hello world!', 'o')); // Hello wrld!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 3. 字符串大小写切换

const caseConvert = (str) => {
  return str.replace(/([a-z]*)([A-Z]*)/g, (m, s1, s2) => {
    return `${s1.toUpperCase()}${s2.toLowerCase()}`;
  });
};
console.log(caseConvert('AbCdE')); // aBcDe
1
2
3
4
5
6

# 4. 去除制表符和换行符的方法

/**
 * \v 匹配垂直制表符
 * \n 换行符 new line
 * \r 回车符 return
 * \t 制表符 tab
 * \f 换页符 form feed
 * \b 退格符 backspace
 * @param str
 * @returns {void | string}
 */
const removeEmpty = (str) => str.replace(/[\t\n\v\r\f]/g, '');
1
2
3
4
5
6
7
8
9
10
11

# 5. 统计某一字符(串)出现的次数

统计某一字符或字符串在另一个字符串中出现的次数

const strCount = (str, target) => str.split(target).length - 1;
1

# 6. script 标签中 defer 和 async 的区别

  • script :会阻碍 HTML 解析,只有下载好并执行完脚本才会继续解析 HTML。
  • async script :解析 HTML 过程中进行脚本的异步下载,下载成功立马执行,有可能会阻断 HTML 的解析,DOMContentLoaded和async脚本不会互相等待
  • defer script:完全不会阻碍 HTML 的解析,解析完成之后再按照顺序执行脚本(DOMContentLoaded事件处理程序等待defer脚本执行完之后执行)
标签 js执行顺序 阻塞解析HTML
scipt 在HTML中的顺序 阻塞
scipt defer 在HTML中的顺序 不阻塞解析
scipt async 网络请求返回顺序 可能阻塞,也可能不阻塞,取决于什么时候下载完成

script标签区别

# 7. 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个

// 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个
class Scheduler {
  // Your code here
  constructor(limit) {
    this.limit = limit;
    this.queue = [];
    this.running = 0;
  }

  add(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({ task, resolve, reject });
      this.run();
    });
  }

  run() {
    while (this.running < this.limit && this.queue.length) {
      const { task, resolve, reject } = this.queue.shift();
      this.running += 1;
      task().then(resolve).catch(reject).finally(() => {
        this.running -= 1;
        this.run();
      });
    }
    if (this.running === 0 && this.queue.length === 0) {
      console.log('All tasks completed');
    }
  }
}

const delay = (timeout) => new Promise((resolve) => {
  setTimeout(resolve, timeout);
});
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
  scheduler.add(() => delay(time)).then(() => console.log(order));
};

addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 8. 实现一个数据打乱的函数

# sort

大部分会使用 sort 结合 Math.random,这种方式并不是真正意思上的乱序,一些元素并没有机会相互比较, 最终数组元素停留位置的概率并不是完全随机的

// 数组乱序
const shuffle = (arr) => {
  return arr.sort(() => Math.random() - 0.5);
};
1
2
3
4

v8在处理sort方法时,使用了插入排序和快排两种方案。当目标数组长度小于10时,使用插入排序;反之,使用快速排序。其实不管用什么排序方法,大多数排序算法的时间复杂度介于O(n)到O(n²)之间,元素之间的比较次数通常情况下要远小于n(n-1)/2,也就意味着有一些元素之间根本就没机会相比较(也就没有了随机交换的可能),这些 sort 随机排序的算法自然也不能真正随机

改造 sort 和 Math.random() 的结合方式

const shuffle = (arr) => {
  const newArr = arr.map((item) => ({ val: item, ram: Math.random() }));
  newArr.sort((a, b) => a.ram - b.ram);
  arr.splice(0, arr.length, ...newArr.map((i) => i.val));
  return arr;
};
1
2
3
4
5
6

# Fisher–Yates

这个算法其实非常的简单,就是将数组从后向前遍历,然后将当前元素与随机位置的元素进行交换

const shuffle = (arr) => {
  for (let i = arr.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
  return arr;
};
1
2
3
4
5
6
7
Last Updated: 8/19/2025, 4:28:59 PM