js手写题:

# js手写题:

# 排序算法

# 冒泡排序

时间复杂度:O(N^2); 空间复杂度:O(1)

思路:

  1. 比较相邻元素:从数组的第一个元素开始,依次比较相邻的两个元素。

    • 如果前一个比后一个大,就交换它们的位置。
  2. 一轮比较完成后:数组的最后一个元素一定是最大的,就像“气泡”一样被“冒”到了最上面。

  3. 重复上述过程

    • 每一轮都会将剩余未排序部分的最大值冒到末尾。
    • 直到所有元素都有序。
function BubbleSort(arr) {
	if(arr == null || arr.length <= 0 ){
		return []
	}
	let len = arr.length
	for(let end = len - 1; end > 0; end--){
		for(let i = 0; i < end; i ++){
			if(arr[i] > arr[i+1] {
				swap(arr, i , i +1)
		}
	}
	return arr
}

function swap(arr, i , j) {
	// var temp = arr[i];
    // arr[i] = arr[j];
    // arr[j] = temp;
    //交换也可以用异或运算符
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

举个例子

[5, 2, 4, 1] 进行冒泡排序:

  • 第一轮:
    • 5 和 2 比,交换 → [2, 5, 4, 1]
    • 5 和 4 比,交换 → [2, 4, 5, 1]
    • 5 和 1 比,交换 → [2, 4, 1, 5] (最大数 5 冒到最后)
  • 第二轮:
    • 2 和 4 比,不交换 → [2, 4, 1, 5]
    • 4 和 1 比,交换 → [2, 1, 4, 5]
    • (次大数 4 冒到倒数第二位)
  • 第三轮:
    • 2 和 1 比,交换 → [1, 2, 4, 5] ✅ 有序

# 选择排序

时间复杂度:O(N^2); 空间复杂度:O(1)

思路:

  1. 从未排序部分中找到最小值(或最大值)
  2. 把这个最小值放到已排序部分的尾部
  3. 重复1和2,直到所有元素有序
function SelectionSort(arr) {
    if(arr == null || arr.length < 0) {
        return []
    }
    for(let i = 0; i < arr.length - 1 ;i++){
        let minIndex = i
        for(let j = i + 1; j < arr.lenght; j++){
            // 找最小的值
            minIndex = arr[j] < arr[minIndex]  ? j :minIndex
        }
        //把这个最小值放到已排序部分的尾部
        swap(arr, i , minIndex)
    }
    return arr
}

举个例子

[5, 2, 4, 1] 进行选择排序(升序):

  • 第一轮:从 [5, 2, 4, 1] 中找最小值 1,放到第一个位置 → [1, 2, 4, 5]
  • 第二轮:从剩余 [2, 4, 5] 中找最小值 2,保持不动 → [1, 2, 4, 5]
  • 第三轮:从 [4, 5] 中找最小值 4,保持不动 → [1, 2, 4, 5]
  • 第四轮:最后只剩 5,排序完成。 ✅

# 插入排序

时间复杂度:O(N^2); 空间复杂度:O(1)

思路:

  1. 把数组分为已排序区和未排序区
  2. 从未排序区中依次取出一个数,插入到已排序区的正确位置(保持有序)
  3. 重复直到未排序区为空

有点像打扑克牌时,手里的一叠牌总是边摸牌边整理

/**
 * 插入排序
 * 思路:将数组分为“已排序区间”和“未排序区间”。
 * 从未排序区间中取出一个数,插入到已排序区间的正确位置。
 * 
 * 时间复杂度:
 *   - 最好情况:O(n) (原本有序)
 *   - 平均/最坏情况:O(n^2)
 * 空间复杂度:O(1) 原地排序
 * 稳定性:稳定(相等元素不会交换顺序)
 */
function insertSort(arr) {
    // 边界处理:空数组或单元素数组,直接返回
    if (arr == null || arr.length <= 0) {
        return [];
    }

    var len = arr.length;

    // 从第二个元素开始(默认第一个元素是已排序区间)
    for (var i = 1; i < len; i++) {
        // 从当前位置向前扫描,找到合适的插入位置
        for (var j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
            // 如果前一个元素比当前元素大,则交换
            swap(arr, j, j + 1);
        }
    }

    return arr;
}

function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

举个例子

[5, 2, 4, 1] 做插入排序:

  • 初始:已排序区 [5],未排序区 [2, 4, 1]
  • 2 插入 → [2, 5] | [4, 1]
  • 4 插入 → [2, 4, 5] | [1]
  • 1 插入 → [1, 2, 4, 5] | [] ✅ 排序完成

# 归并排序

思路:

  1. 分解(Divide)

    • 不断把数组对半拆分,直到每个子数组只剩 1 个元素(天然有序)。
  2. 合并(Conquer)

    • 从最小的子数组开始,两两合并成有序数组。
    • 在合并过程中,利用两个子数组都已经有序的性质,依次比较并放入新数组。

「先分成碎片,再把碎片有序地拼起来」

/**
 * 归并排序(循环版,自底向上)
 * 思路:
 * 1. 一开始将每个元素看作长度为1的有序子数组。
 * 2. 每次按子数组长度(1,2,4,8...)两两合并。
 */
function mergeSortIterative(arr) {
    if (!arr || arr.length <= 1) return arr;
    const n = arr.length;
    let step = 1;

    // step 表示子数组的长度,每次翻倍
    while (step < n) {
        for (let i = 0; i < n; i += 2 * step) {
            const left = arr.slice(i, i + step);
            const right = arr.slice(i + step, i + 2 * step);
            const merged = merge(left, right);

            // 把合并好的数组放回原数组对应位置
            for (let k = 0; k < merged.length; k++) {
                arr[i + k] = merged[k];
            }
        }
        step *= 2; // 子数组长度翻倍
    }
    return arr;
}

/**
 * 归并排序(递归版,自顶向下)
 * 思路:
 * 1. 分:不断把数组二分,直到子数组长度为 1。
 * 2. 治:合并两个有序子数组。
 */
function mergeSortRecursive(arr) {
    if (!arr || arr.length <= 1) return arr;

    // 拆分:取中点
    const mid = Math.floor(arr.length / 2);
    const left = mergeSortRecursive(arr.slice(0, mid));
    const right = mergeSortRecursive(arr.slice(mid));

    // 合并:两个有序数组
    return merge(left, right);
}

/**
 * 合并两个有序数组
 */
function merge(left, right) {
    const result = [];
    let i = 0, j = 0;

    // 逐个比较,较小的先放入结果
    while (i < left.length && j < right.length) {
        if (left[i] <= right[j]) {
            result.push(left[i++]);
        } else {
            result.push(right[j++]);
        }
    }

    // 剩余部分直接拼接
    return result.concat(left.slice(i)).concat(right.slice(j));
}

# 堆排序

堆排序的核心思想是利用**完全二叉树结构的堆(最大堆/最小堆)**来实现排序。

堆的定义

  • 最大堆:每个父节点的值都大于或等于子节点。
  • 最小堆:每个父节点的值都小于或等于子节点。 堆排序通常用最大堆,因为我们每次把最大值“冒”到根(arr[0])。

堆排序步骤

  • 建堆:先把数组调整成一个最大堆。
  • 排序过程
    1. 把堆顶(最大值)和最后一个元素交换。
    2. 堆的有效长度减一(相当于把最后一个元素放到正确位置)。
    3. 重新调整剩余部分为最大堆。
    4. 重复这个过程,直到堆的大小为 1。

时间复杂度

  • 建堆:O(n)
  • 每次调整堆:O(log n),执行 n-1 次
  • 总复杂度:O(n log n)
  • 堆排序是原地排序,空间复杂度 O(1)。

假设数组 [5, 2, 9, 1, 3]

  1. 建最大堆 → [9, 5, 2, 1, 3]
  2. 交换堆顶和最后一个 → [3, 5, 2, 1, 9]
  3. 调整堆 → [5, 3, 2, 1, 9]
  4. 再交换堆顶和倒数第二个 → [1, 3, 2, 5, 9]
  5. 调整堆 → [3, 1, 2, 5, 9]
  6. 如此继续,最终得到 [1, 2, 3, 5, 9]
function heapSort(arr) {
  if (!arr || arr.length <= 1) return arr

  let n = arr.length

  // 1. 建堆(从最后一个非叶子节点开始调整)
  for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
    heapify(arr, n, i)
  }

  // 2. 不断取出堆顶元素放到最后,然后调整剩余部分
  for (let i = n - 1; i > 0; i--) {
    swap(arr, 0, i) // 最大值交换到末尾
    heapify(arr, i, 0) // 重新调整堆(只调整前 i 个)
  }

  return arr
}

// 调整堆函数
function heapify(arr, heapSize, i) {
  let largest = i
  let left = 2 * i + 1
  let right = 2 * i + 2

  // 左子节点比当前大
  if (left < heapSize && arr[left] > arr[largest]) {
    largest = left
  }

  // 右子节点比当前大
  if (right < heapSize && arr[right] > arr[largest]) {
    largest = right
  }

  // 如果最大值不是父节点,交换并递归调整
  if (largest !== i) {
    swap(arr, i, largest)
    heapify(arr, heapSize, largest)
  }
}

# 快速排序

快速排序是一种 分治法 排序算法

  1. 选择一个基准值(pivot)(通常取数组第一个或最后一个元素)。

  2. 分区(partition):把数组分成两部分:

    • 小于基准值的放左边
    • 大于基准值的放右边
  3. 递归排序 左右两部分。

  4. 合并结果(左右部分 + 基准值)。

平均时间复杂度:O(n log n)

最坏时间复杂度:O(n²)(当数组本身有序且选基准值不佳时)

空间复杂度:O(log n)(递归栈空间)

排序 [5, 2, 9, 1, 3]

  1. 3 为基准:分成 [2, 1] 3 [5, 9]
  2. 左边 [2, 1] → 排序 → [1, 2]
  3. 右边 [5, 9] → 排序 → [5, 9]
  4. 合并 → [1, 2, 3, 5, 9]

写法 1:简单版(递归 + 额外数组)

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

  let pivot = arr[arr.length - 1]  // 选择最后一个元素作为基准
  let left = []
  let right = []

  for (let i = 0; i < arr.length - 1; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i])
    } else {
      right.push(arr[i])
    }
  }

  return [...quickSort(left), pivot, ...quickSort(right)]
}

// 测试
console.log(quickSort([5, 2, 9, 1, 3])) // [1, 2, 3, 5, 9]

写法 2:原地交换(更高效)

function quickSort(arr, left = 0, right = arr.length - 1) {
  if (left < right) {
    let pivotIndex = partition(arr, left, right)
    quickSort(arr, left, pivotIndex - 1)  // 排序左边
    quickSort(arr, pivotIndex + 1, right) // 排序右边
  }
  return arr
}

function partition(arr, left, right) {
  let pivot = arr[right] // 基准值
  let i = left - 1

  for (let j = left; j < right; j++) {
    if (arr[j] <= pivot) {
      i++
      swap(arr, i, j)
    }
  }
  swap(arr, i + 1, right)
  return i + 1
}

// 测试
console.log(quickSort([5, 2, 9, 1, 3])) // [1, 2, 3, 5, 9]

# 数组去重

有7种方法(set、)

# 利用Set()+Array.from()

  • Set对象:是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即Set中的元素是唯一的
  • Array.from() 方法:对一个类似数组可迭代对象创建一个新的,浅拷贝的数组实例。
const result = Array.from(new Set(arr))
// 或者
const result = arr => [...new Set(arr)]

NaNundefined类型去重也是有效的,是因为NaNundefined都可以被存储在Set中, NaN之间被视为相同的值(尽管在js中:NaN !== NaN)。

# 利用两层循环+数组的splice方法

通过两层循环对数组元素进行逐一比较,然后通过splice方法来删除重复的元素。此方法对NaN是无法进行去重的,因为进行比较时NaN !== NaN

function removeDuplicate(arr) {
    let len = arr.length
    for(let i = 0; i < len; i++){
        for(let j = i+1; j < len; j++){
            if(arr[i] === arr[j]) {
                arr.splice(j,1)
                len-- // 减少循环次数提高性能
                j-- // 保证j的值自加后不变
            }
        }
    }
    return arr
}

# 利用数组的indexOf方法

新建一个空数组,遍历需要去重的数组,将数组元素存入新数组中,存放前判断数组中是否已经含有当前元素,没有则存入。此方法也无法对NaN去重。

  • indexOf() 方法:返回调用它的String对象中第一次出现的指定值的索引,从 fromIndex 处进行搜索。如果未找到该值,则返回 -1。
function removeDuplicate(arr) {
    let result = []
    for(let i = 0; i < arr.length; i++){
        if(!result.indexOf(arr[i])){
            result.push(arr[i])
        }
    }
    return result
}

# 利用数组的includes方法

此方法逻辑与indexOf方法去重异曲同工,只是用includes方法来判断是否包含重复元素。

  • includes()方法:用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false
function removeDuplicate(arr) {
    let result = []
    arr.forEach(item => {
        if(!result.includes(item)){
            result.push(item)
        }
    })
    return result
}

注意:为什么includes能够检测到数组中包含NaN,其涉及到includes底层的实现

在进行判断是否包含某元素时会调用sameValueZero方法进行比较,如果为NaN,则会使用isNaN()进行转化。

# 利用数组的filter()+indexOf()

filter方法会对满足条件的元素存放到一个新数组中,结合indexOf方法进行判断。

  • filter() 方法:会创建一个新数组,其包含通过所提供函数实现的测试的所有元素。
function removeDuplicate(arr) {
  return arr.filter((item, index) => {
    return arr.indexOf(item) === index
  })
}

# 利用Map()

Map对象是JavaScript提供的一种数据结构,结构为键值对形式,将数组元素作为map的键存入,然后结合has()set()方法判断键是否重复。

  • Map 对象:用于保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值)都可以作为一个键或一个值。
function removeDuplicate(arr) {
    const map = new Map()
    const result = []
    arr.forEach(item => {
        if(!map.has(item)){
            map.set(item)
            result.push(item)
        }
    })
    return result
}

注意:使用Map()也可对NaN去重,原因是Map进行判断时认为NaN是与NaN相等的,剩下所有其它的值是根据 === 运算符的结果判断是否相等。

# 利用对象

其实现思想和Map()是差不多的,主要是利用了对象的属性名不可重复这一特性。

function removeDuplicate(arr) {
    const result = []
    const obj = {}
    
    arr.forEach(item => {
        if(!obj[item]){
            result.push(item)
            obj[item] = true
        }
    })
    return result
}

# 版本号排序

题目: 输入一组版本号,输出从大到小的排序 输入: [‘2.1.0.1’, ‘0.402.1’, ‘10.2.1’, ‘5.1.2’, ‘1.0.4.5’] 输出: [‘10.2.1’, ‘5.1.2’, ‘2.1.0.1’, ‘1.0.4.5’, ‘0.402.1’]

思路:肯定用的是排序,选用split分割,遍历判断:

(1)其中一个arr[i]undefined说明前面判断的都相同,则直接判断arr.length; (2)arr[i]相同则continue (3)不同则直接return arr2[i]-arr1[i] 不用转成number

function versionSort(arr){
    arr.sort((a,b) => {
        let i = 0;
        let arr1 = a.split(".")
        let arr2 = b.split(".")
        
        while(true){
            // 取出相同位置的数字
      		const s1 = arr1[i];
      		const s2 = arr2[i];
      		i++;
            if(s1 === undefined || s2 === undefined){
                // 比较长度
                return s1.length - s2.length
            }
            
            if(s1 === s2) continue
            
            return s1 - s2
        }
    })
}

# map计数类型

使用对象: 涉及到需要计算数组或字符串中某个元素的数量时

步骤: 一般定义一个map,遍历数组或字符串,利用counter[s[i]] ? counter[s[i]]++ : (counter[s[i]] = 1); 计数

# 3.1存在重复元素

题目描述如下:

给定一个整数数组,判断是否存在重复元素。如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false

function judge(num) {
    let map = new Map()
    for(let i of num){
        if(map.has(i)){
            return true
        }else{
            map.set(i,1)
        }
    }
    return false
}

# 字符串第一个唯一字符

题目:给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。 思路:用map。 定义一个对象。 遍历字符串,元素出现一次就+1 map[s] = (map[s] || 0) + 1;.最后再次遍历字符串,找到第一次map[str[i]] === 1

function firstUniqChar(str) {
    let map = {}
    for(let s of str) {
        map[s] = (map[s] || 0) + 1
    }
    for(let i = 0; i < str.length; i++){
        if(map[str[i]] === 1)	return i
    }
    return -1
}

# map存储

# 4.1:两数之和

题目:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

思路:用map。 每遍历一个元素,看看map 中是否存在target-nums[i],存在则返回[map.get(element),i],不存在则加入当前元素到map

var twoSum = function(nums, target) {
    let map = new Map()
    for(let i= 0; i < nums.length; i++){
        if(map.has(target - nums[i]){
            return [map.get(target - nums[i]),i]
        } else{
            map.set(nums[i], i)
        }
    }
	return []
}

# 4.2:两数组交集

题目: 给定两个数组,编写一个函数来计算它们的交集。(交集里的元素互不相同) 思路:定义一个对象map={}。遍历arr1,设置map[arr1[i]]=true。遍历arr2,对每个map[arr2[i]]==true的元素记录,并设置当前map[arr2[i]]=false避免重复。

// 交集就是两个集合共同的
function intersection(arr1, arr2) {
   let map = {}
   let res = {}
   for (let i = 0; i < arr1.length; i++) {
       map[arr1[i]] = true
   }
   for (let i = 0 ; i < arr2.length; i++) {
       if(map[arr2[i]]){
           res.push(arr2[i])
           map[arr2[i]] = false // 避免重复
       }
   }
    return res
}

用filter:

function intersection(arr1, arr2) {
  const duplicates = arr1.filter(item => arr2.includes(item));
  return duplicates;
}

# 递归

适用对象: 一个大问题很复杂,但是可以拆分为很多个小问题,只要小问题都解决了,那么大问题就解决了。搞个数组(一维、二维),从最小的问题一直计算到最大的问题。

而递归是大问题到小问题且不需要记录过程值

一般解题步骤: 第一步骤:定义数组元素的含义 第二步骤:找出数组元素之间的关系式 : 利用 dp[n-1],dp[n-2]……dp[1], 来推出 dp[n] ,一般找dp[i-1],dp[i-2]与dp[i]的关系 第三步骤:找出初始值 :dp[0]、dp[1]、dp[2]等 总结一下就是:写出递归公式,找到终止条件

# promise封装

# promise封装setTimeout,间隔打印1 1 1

思路解析

  1. 封装 setTimeout 成一个 Promise,叫 delay,可以用来 await
  2. 使用 async/await,在循环里顺序等待 delay
  3. 这样就能实现“每隔一段时间打印一次”。

代码

async/await版本


function delayPrint(timeout) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('1');
      resolve();
    }, timeout);
  });
}

// 按间隔打印 1 1 1
async function printOnInterval() {
    for(let i = 0; i < 3; i++){
        await delayPrint(1000);
    }
}
printOnInterval()

Promise 链式调用:

function delayedLog(message, delay) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(message);
      resolve();
    }, delay);
  });
}
function printOne() {
  delayedLog('1', 1000)
    .then(() => delayedLog('1', 1000))
    .then(() => delayedLog('1', 1000));
}
printOne();

# promsie实现每隔一秒输出1,2,3

# 手搓promise

我会先写一个 class MyPromise,在构造函数里定义三个东西:

  • 一个 state 初始是 "pending"
  • 一个 value 保存成功结果;
  • 一个 reason 保存失败原因; 同时还会有两个数组,onFulfilledonRejected,用来存放回调。

接着我会写 resolvereject 两个函数:

  • resolve 会判断如果还是 pending,就把状态改成 "fulfilled",把值保存下来,然后把成功回调依次执行。
  • reject 也是类似逻辑,把状态改成 "rejected",保存 reason,然后把失败回调依次执行。

然后 then 方法我会写在原型上:

  • 它接收两个参数 onFulfilledonRejected,我会做一个容错,如果不是函数就做透传。
  • then 会返回一个新的 MyPromise,这是为了支持链式调用。
  • 在新的 Promise 里,如果当前状态已经是 fulfilled,就用 setTimeout 异步执行 onFulfilled,把结果交给下一个 Promise 的 resolve。
  • 如果是 rejected 就执行 onRejected,同样把结果交给下一个 Promise。
  • 如果还在 pending,就把这两个回调暂存进数组,等到状态改变再异步执行。

这样基本上一个最小可用的 Promise 就完成了,能支持状态管理、回调存储和链式调用。

 // ====================== 手搓一个promise======================

    // 要点:Promise就是一个状态机,处理异步回调问题
    // 三个关键点就是
    // 状态管理,回调,链式调用
    class MyPromise {
      constructor(executor) {
        this.state = 'pending' // 初始状态
        this.value = undefined // 成功的值
        this.reason = undefined // 失败的值
        this.onFulfilled = [] // 保证 p 不是 Promise 时也能处理
        this.onRejected = [] // 存放失败的回调

        //  resolve 函数,用来修改状态为 fulfilled
        const resolve = (value) => {
          if (this.state === 'pending') {
            this.state = 'fulfulled' // 只能由 pending -> fulfilled
            this.value = value
            // 执行所有成功回调(可能有多个 then 注册)
            this.onFulfilled.forEach((fn) => fn())
          }
        }
        // reject 函数,用来修改状态为 rejected
        const reject = (reason) => {
          if (this.state === 'pending') {
            this.state = 'rejected'
            this.reason = reason
            // 执行所有失败回调
            this.onRejected.forEach((fn) => fn())
          }
        }
        // 立即执行传入的 executor 函数
        // 如果执行过程中抛出错误,直接 reject
        try {
          executor(resolve, reject)
        } catch (err) {
          reject(err)
        }
      }
      // then 方法:支持链式调用
      then(onFulfilled, onRejected) {
        // 防止 onFulfilled / onRejected 不是函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v
        onRejected =
          typeof onRejected === 'function'
            ? onRejected
            : (e) => {
                throw e
              }

        // 返回新的 Promise,支持链式调用
        return new MyPromise((resolve, reject) => {
          // 如果当前是 fulfilled 状态,异步执行 onFulfilled
          if (this.state === 'fullilled') {
            setTimeout(() => {
              try {
                const x = onFulfilled(this.value) // 执行回调
                resolve(x) // 把结果交给下一个 Promise
              } catch (err) {
                reject(err) // 出错交给下一个 Promise 的 reject
              }
            })
          }
          // 如果当前是 rejected 状态,异步执行 onRejected
          if (this.state === 'rejected') {
            setTimeout(() => {
              try {
                const x = onRejected(this.reason)
                resolve(x)
              } catch (err) {
                reject(err)
              }
            })
          }
          // 如果当前是 pending 状态,需要把回调存起来(发布订阅)
          if (this.state === 'pending') {
            this.onFulfilled.push(() => {
              setTimeout(() => {
                try {
                  const x = onFulfilled(this.value)
                  resolve(x) // 把结果交给下一个 Promise
                } catch (err) {
                  reject(err) // 出错交给下一个 Promise 的 reject
                }
              })
            })

            this.onRejected.push(() => {
              setTimeout(() => {
                try {
                  const x = onRejected(this.reason)
                  resolve(x)
                } catch (err) {
                  reject(err)
                }
              })
            })
          }
        })
      }
    }

    // new MyPromise((resolve, reject) => {
    //   setTimeout(() => resolve('Hello mmx!'), 500)
    // })
    //   .then((res) => {
    //     console.log('第一次 then:', res)
    //     return res + ' 🚀'
    //   })
    //   .then((res) => {
    //     console.log('第二次 then:', res)
    //   })
 // =======promise封装===============
    function myFetch(url, option = {}) {
      return new Promise((resolve, reject) => {
        fetch(url, option)
          .then((response) => {
            if (!response.ok) {
              reject(new Error(`HTTP Error: ${response.status}`))
            }
            return response.json() // 自动解析json
          })
          .then((data) => resolve(data))
          .catch((err) => reject(err))
      })
    }
// ===== 手写 Promise.all =====
function myPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    // 1. 参数必须是数组,否则报错
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'))
    }

    const results = []       // 用来存放每个 Promise 的结果
    let completedCount = 0   // 已完成的数量

    // 如果传进来的是空数组,直接返回空结果
    if (promises.length === 0) {
      return resolve([])
    }

    // 2. 遍历数组中的每一个 Promise
    promises.forEach((p, index) => {
      // 用 Promise.resolve 包一层,保证即使 p 不是 Promise 也能被当成 Promise 处理
      Promise.resolve(p).then(
        (value) => {
          // 成功时,保存结果到对应位置(保证顺序一致)
          results[index] = value
          completedCount++

          // 如果所有 Promise 都完成了,就 resolve 整个数组
          if (completedCount === promises.length) {
            resolve(results)
          }
        },
        (err) => {
          // 只要有一个失败,就立刻 reject
          reject(err)
        }
      )
    })
  })
}

Promise.allSettled

  • 所有 Promise 都完成(无论是成功还是失败)。
  • 最终返回一个数组,数组里每一项都描述了对应 Promise 的结果:
    • 成功 → { status: 'fulfilled', value: xxx }
    • 失败 → { status: 'rejected', reason: xxx }
function myPromiseAllSettled(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'))
    }

    const results = []
    let completedCount = 0

    if (promises.length === 0) {
      return resolve([])
    }

    promises.forEach((p, index) => {
      Promise.resolve(p).then(
        (value) => {
          results[index] = { status: "fulfilled", value }
          completedCount++
          if (completedCount === promises.length) {
            resolve(results)
          }
        },
        (err) => {
          results[index] = { status: "rejected", reason: err }
          completedCount++
          if (completedCount === promises.length) {
            resolve(results)
          }
        }
      )
    })
  })
}

css手写题:

场景

代码输出题