leetcode----JavaScript 详情题解(4)

2023-11-06

目录

2722. 根据 ID 合并两个数组

2723. 添加两个 Promise 对象

2724. 排序方式

2725. 间隔取消

2726. 使用方法链的计算器

2727. 判断对象是否为空

2624. 蜗牛排序 

 2694. 事件发射器


2722. 根据 ID 合并两个数组

现给定两个数组 arr1 和 arr2 ,返回一个新的数组 joinedArray 。两个输入数组中的每个对象都包含一个 id 字段。joinedArray 是一个通过 id 将 arr1 和 arr2 连接而成的数组。joinedArray 的长度应为唯一值 id 的长度。返回的数组应按 id 升序 排序。

如果一个 id 存在于一个数组中但不存在于另一个数组中,则该对象应包含在结果数组中且不进行修改。

如果两个对象共享一个 id ,则它们的属性应进行合并:

  • 如果一个键只存在于一个对象中,则该键值对应该包含在对象中。
  • 如果一个键在两个对象中都包含,则 arr2 中的值应覆盖 arr1 中的值。

示例

输入:
arr1 = [
    {"id": 1, "x": 1},
    {"id": 2, "x": 9}
], 
arr2 = [
    {"id": 3, "x": 5}
]
输出:
[
    {"id": 1, "x": 1},
    {"id": 2, "x": 9},
    {"id": 3, "x": 5}
]
解释:没有共同的 id,因此将 arr1 与 arr2 简单地连接起来。

题解


var join = function (arr1, arr2) {
    // 合并两个数组
    const mergedArray = arr1.concat(arr2);

    // 建立一个用于存储结果的 Map
    const resultMap = new Map();

    // 遍历合并后的数组
    for (const obj of mergedArray) {
        // 获取当前对象的 id
        const id = obj.id;

        // 如果 resultMap 中已经存在当前 id,则合并对象的属性
        if (resultMap.has(id)) {
            const existingObj = resultMap.get(id);
            resultMap.set(id, { ...existingObj, ...obj });
        }
        // 否则,将当前对象作为新的键值对添加到 resultMap 中
        else {
            resultMap.set(id, { ...obj });
        }
    }

    // 将 resultMap 中的对象按 id 升序转换成数组
    const joinedArray = Array.from(resultMap.values()).sort((a, b) => a.id - b.id);

    return joinedArray;
}

这段代码定义了一个函数 join,接受两个数组作为参数 arr1 和 arr2

代码首先通过 arr1.concat(arr2) 将两个数组合并成一个数组 mergedArray

接下来,代码创建了一个用于存储结果的 Map 对象 resultMap

然后,代码通过遍历合并后的数组 mergedArray,获取每个对象的属性 id

如果 resultMap 中已经存在当前 id,则将已存在的对象与当前对象进行属性合并,并将合并后的对象重新存入 resultMap 中。

否则,将当前对象作为新的键值对添加到 resultMap 中。

最后,将 resultMap 中的对象按照 id 进行升序排列,并转换为数组 joinedArray

最后,函数返回 joinedArray

2723. 添加两个 Promise 对象

给定两个 promise 对象 promise1 和 promise2,返回一个新的 promise。promise1 和 promise2 都会被解析为一个数字。返回的 Promise 应该解析为这两个数字的和。

示例

输入:
promise1 = new Promise(resolve => setTimeout(() => resolve(2), 20)), 
promise2 = new Promise(resolve => setTimeout(() => resolve(5), 60))
输出:7
解释:两个输入的 Promise 分别解析为值 2 和 5。返回的 Promise 应该解析为 2 + 5 = 7。返回的 Promise 解析的时间不作为判断条件。

题解

var addTwoPromises = async function (promise1, promise2) {
    return Promise.all([promise1, promise2])
        .then(([num1, num2]) => {
            const sum = num1 + num2;
            return Promise.resolve(sum);
        });
};

这段代码定义了一个名为addTwoPromises的异步函数,它接受两个参数promise1和promise2。函数内部利用Promise.all方法,将这两个参数传入一个数组中,并返回一个新的Promise。在这个Promise被解析后,通过回调函数中的解构赋值将num1和num2分别赋值为数组中的两个元素。然后,通过将num1和num2相加得到sum,并通过Promise.resolve方法返回一个解析后的Promise。最后,这个解析后的Promise的值将作为addTwoPromises函数的返回值。、

2724. 排序方式

给定一个数组 arr 和一个函数 fn,返回一个排序后的数组 sortedArr。你可以假设 fn 只返回数字,并且这些数字决定了 sortedArr 的排序顺序。sortedArr 必须按照 fn 的输出值 升序 排序。

你可以假设对于给定的数组,fn 不会返回重复的数字。

示例

输入:arr = [5, 4, 1, 2, 3], fn = (x) => x
输出:[1, 2, 3, 4, 5]
解释:fn 只是返回传入的数字,因此数组按升序排序。

题解


var sortBy = function (arr, fn) {
    // 使用数组的 sort 方法进行排序
    arr.sort((a, b) => {
        // 根据 fn 函数的返回值进行比较
        const valA = fn(a);
        const valB = fn(b);
        if (valA < valB) {
            return -1;
        } else if (valA > valB) {
            return 1;
        } else {
            return 0;
        }
    });

    // 返回排序后的数组
    return arr;
};

 

这段代码定义了一个名为sortBy的函数,该函数接收两个参数arr和fn。arr是要排序的数组,fn是用于返回比较值的函数。

在sortBy函数内部,使用数组的sort方法对arr进行排序。sort方法接受一个比较函数作为参数,该函数用于确定元素的排序顺序。

比较函数使用fn函数的返回值来进行元素比较。首先,将fn应用于数组中的两个元素a和b,得到valA和valB。然后,根据valA和valB的大小关系进行比较。如果valA小于valB,返回-1,表示a应该在b之前。如果valA大于valB,返回1,表示b应该在a之前。如果valA等于valB,返回0,表示a和b的相对位置不变。

最后,sortBy函数返回排序后的数组arr。

2725. 间隔取消

现给定一个函数 fn,一个参数数组 args 和一个时间间隔 t,返回一个取消函数 cancelFn

函数 fn 应该立即使用 args 调用,并且在每个 t 毫秒内再次调用,直到调用 cancelFn

示例

输入:fn = (x) => x * 2, args = [4], t = 20, cancelT = 110
输出:
[
   {"time": 0, "returned": 8},
   {"time": 20, "returned": 8},
   {"time": 40, "returned": 8},
   {"time": 60, "returned": 8},
   {"time": 80, "returned": 8},
   {"time": 100, "returned": 8}
]
解释: 
const cancel = cancellable(x => x * 2, [4], 20);
setTimeout(cancel, cancelT);
每隔 20ms,调用 fn(4)。
第一次调用 fn 是在 0ms。fn(4) 返回 8。
第二次调用 fn 是在 20ms。fn(4) 返回 8。
第三次调用 fn 是在 40ms。fn(4) 返回 8。
第四次调用 fn 是在 60ms。fn(4) 返回 8。
第五次调用 fn 是在 80ms。fn(4) 返回 8。
第六次调用 fn 是在 100ms。fn(4) 返回 8。
在 t=110ms 时取消。

题解

var cancellable = function (fn, args, t) {
    // 调用初始函数
    fn(...args);
    const intervalId = setInterval(() => {
        fn(...args);
    }, t);

    // 返回取消函数
    const cancelFn = () => {
        clearInterval(intervalId);
    };

    return cancelFn;
};

这段代码定义了一个名为cancellable的函数,它接受三个参数:fn(函数)、args(函数的参数数组)和t(时间间隔)。这段代码的作用是创建一个可取消的定时器。

首先,在函数内部,我们通过调用初始函数fn并传入参数args来立即执行一次函数。

然后,我们使用setInterval函数创建了一个定时器,该定时器会每隔t毫秒调用一次函数fn并传入参数args。

接下来,我们定义了一个匿名函数cancelFn,它的作用是清除之前设定的定时器,通过调用clearInterval函数并传入定时器的ID(intervalId)来实现。

最后,我们将cancelFn函数作为返回值,以便用户调用并取消之前设定的定时器。

所以,这段代码的功能是创建一个可取消的定时器,可以设置定时器的间隔时间,并提供一个函数用于取消定时器。

2726. 使用方法链的计算器

设计一个类 Calculator 。该类应提供加法、减法、乘法、除法和乘方等数学运算功能。同时,它还应支持连续操作的方法链式调用。Calculator 类的构造函数应接受一个数字作为 result 的初始值。

你的 Calculator 类应包含以下方法:

  • add - 将给定的数字 value 与 result 相加,并返回更新后的 Calculator 对象。
  • subtract - 从 result 中减去给定的数字 value ,并返回更新后的 Calculator 对象。
  • multiply - 将 result 乘以给定的数字 value ,并返回更新后的 Calculator 对象。
  • divide - 将 result 除以给定的数字 value ,并返回更新后的 Calculator 对象。如果传入的值为 0 ,则抛出错误 "Division by zero is not allowed" 。
  • power - 计算 result 的幂,指数为给定的数字 value ,并返回更新后的 Calculator 对象。(result = result ^ value )
  • getResult - 返回 result 的值。

结果与实际结果相差在 10-5 范围内的解被认为是正确的。

输入:actions = ["Calculator", "add", "subtract", "getResult"], values = [10, 5, 7]
输出:8
解释:
new Calculator(10).add(5).subtract(7).getResult() // 10 + 5 - 7 = 8

题解

class Calculator {
  constructor(result) {
    this.result = result;
  }

  add(value) {
    this.result += value;
    return this;
  }

  subtract(value) {
    this.result -= value;
    return this;
  }

  multiply(value) {
    this.result *= value;
    return this;
  }

  divide(value) {
    if (value === 0) {
      throw new Error("Division by zero is not allowed");
    }
    this.result /= value;
    return this;
  }

  power(value) {
    this.result = Math.pow(this.result, value);
    return this;
  }

  getResult() {
    return this.result;
  }
}

这段代码定义了一个名为Calculator的类,它具有以下功能:

  • constructor(result): 这是一个构造函数,用于初始化Calculator类的实例并设置result属性的初始值。

  • add(value): 这是一个方法,用于将给定的value添加到result属性的值上,然后返回当前Calculator实例。这使得可以链式调用这个方法。

  • subtract(value): 这是一个方法,用于从result属性的值中减去给定的value,然后返回当前Calculator实例。

  • multiply(value): 这是一个方法,用于将result属性的值乘以给定的value,然后返回当前Calculator实例。

  • divide(value): 这是一个方法,用于将result属性的值除以给定的value,然后返回当前Calculator实例。如果value为0,则抛出一个错误。

  • power(value): 这是一个方法,用于将result属性的值提升到给定的value次方,然后返回当前Calculator实例。

  • getResult(): 这是一个方法,用于返回result属性的当前值。

通过使用这些方法,可以使用Calculator类进行基本的计算操作,并将多个操作链接在一起来获得最终结果。

2727. 判断对象是否为空

给定一个对象或数组,判断它是否为空。

  • 一个空对象不包含任何键值对。
  • 一个空数组不包含任何元素。

你可以假设对象或数组是通过 JSON.parse 解析得到的。

示例

输入:obj = {"x": 5, "y": 42}
输出:false
解释:The object has 2 key-value pairs so it is not empty.

题解

function isEmpty(data) {
  if (Array.isArray(data)) {
    return data.length === 0;
  }

  if (typeof data === 'object' && data !== null) {
    return Object.keys(data).length === 0;
  }

  return false; // 非数组和对象的其他类型不算为空
}

这段代码定义了一个名为isEmpty的函数,该函数判断给定的参数data是否为空。首先,通过使用Array.isArray()方法判断data是否为数组,如果是,则返回data的长度是否为0,即判断数组是否为空。接下来,通过使用typeof运算符判断data的类型是否为对象并且不为null,如果是,则使用Object.keys()方法获取data的所有属性名组成的数组,并判断该数组的长度是否为0,即判断对象是否为空。最后,如果data既不是数组也不是对象,则返回false,表示其他类型的参数不视为空。

2624. 蜗牛排序 

请你编写一段代码为所有数组实现  snail(rowsCount,colsCount) 方法,该方法将 1D 数组转换为以蜗牛排序的模式的 2D 数组。无效的输入值应该输出一个空数组。当 rowsCount * colsCount !==nums.length 时。这个输入被认为是无效的。

蜗牛排序从左上角的单元格开始,从当前数组的第一个值开始。然后,它从上到下遍历第一列,接着移动到右边的下一列,并从下到上遍历它。将这种模式持续下去,每列交替变换遍历方向,直到覆盖整个数组。例如,当给定输入数组  [19, 10, 3, 7, 9, 8, 5, 2, 1, 17, 16, 14, 12, 18, 6, 13, 11, 20, 4, 15] ,当 rowsCount = 5 且 colsCount = 4 时,需要输出矩阵如下图所示。注意,矩阵沿箭头方向对应于原数组中数字的顺序

 示例

输入:
nums = [19, 10, 3, 7, 9, 8, 5, 2, 1, 17, 16, 14, 12, 18, 6, 13, 11, 20, 4, 15]
rowsCount = 5
colsCount = 4
输出:
[
 [19,17,16,15],
 [10,1,14,4],
 [3,2,12,20],
 [7,5,18,11],
 [9,8,6,13]
]

题解 

Array.prototype.snail = function (rowsCount, colsCount) {
    //数组长度是否等于行数乘以列数,若不相等,则返回一个空数组。
    if (this.length !== rowsCount * colsCount) {
        return [];
    }
    const res = [];

    for (let i = 0; i < rowsCount; i++) {
        res.push([]);
    }
    let seq = true; // seq初始值为true表示正向
    let start = 0; //start初始值为0。
    for (let i = 0; i < this.length; i++) {
        res[start].push(this[i]);
        // 正向
         if (seq) {
            if (start === rowsCount - 1) {
                // 若等于,则将seq变量设为false,表示切换到逆向添加;
                seq = false;
            } else {
                // 若不等于,则将start加1。
                start++;
            }
        } else {
            // 逆向
            if (start === 0) {
                // 判断start是否等于0,若等于,则将seq变量设为true表示切换到正向添加;
                seq = true;
            } else {
                // 若不等于,则将start减1
                start--;
            }
        }
    }
    return res;
}

这段代码定义了一个名为snail的数组方法。这个方法接受两个参数,分别是行数rowsCount和列数colsCount。

首先判断调用该方法的数组长度是否等于行数乘以列数,若不相等,则返回一个空数组。

接下来,创建一个空数组res用来保存结果。使用for循环,循环行数次,并在每次循环中向res数组中添加一个空数组,相当于创建了一个行数*列数的二维数组。

接着定义两个变量seq和start,seq初始值为true表示正向,start初始值为0。

使用第二个for循环,循环遍历调用该方法的数组。在每次循环中,将当前元素添加到res数组中的第start行。

根据seq变量的值,判断是正向添加还是逆向添加。若为正向(seq为true),则判断start是否等于行数-1,若等于,则将seq变量设为false,表示切换到逆向添加;若不等于,则将start加1。

若为逆向(seq为false),则判断start是否等于0,若等于,则将seq变量设为true,表示切换到正向添加;若不等于,则将start减1。

最后,返回res数组作为结果。

简单来说,这段代码的作用是将一个一维数组按照螺旋状排列成一个二维数组,并返回这个二维数组作为结果。

 2694. 事件发射器

设计一个 EventEmitter 类。这个接口与 Node.js 或 DOM 的 Event Target 接口相似,但有一些差异。EventEmitter 应该允许订阅事件和触发事件。

你的 EventEmitter 类应该有以下两个方法:

  • subscribe - 这个方法接收两个参数:一个作为字符串的事件名和一个回调函数。当事件被触发时,这个回调函数将被调用。 一个事件应该能够有多个监听器。当触发带有多个回调函数的事件时,应按照订阅的顺序依次调用每个回调函数。应返回一个结果数组。你可以假设传递给 subscribe 的回调函数都不是引用相同的。 subscribe 方法还应返回一个对象,其中包含一个 unsubscribe 方法,使用户可以取消订阅。当调用 unsubscribe 方法时,回调函数应该从订阅列表中删除,并返回 undefined。
  • emit - 这个方法接收两个参数:一个作为字符串的事件名和一个可选的参数数组,这些参数将传递给回调函数。如果没有订阅给定事件的回调函数,则返回一个空数组。否则,按照它们被订阅的顺序返回所有回调函数调用的结果数组。

示例 

输入:actions = ["EventEmitter", "emit", "subscribe", "subscribe", "emit"], values = [[], ["firstEvent", "function cb1() { return 5; }"],  ["firstEvent", "function cb1() { return 5; }"], ["firstEvent"]]
输出:[[],["emitted",[]],["subscribed"],["subscribed"],["emitted",[5,6]]]
解释:
const emitter = new EventEmitter();
emitter.emit("firstEvent"); // [], 还没有订阅任何回调函数
emitter.subscribe("firstEvent", function cb1() { return 5; });
emitter.subscribe("firstEvent", function cb2() { return 6; });
emitter.emit("firstEvent"); // [5, 6], 返回 cb1 和 cb2 的输出

题解

class EventEmitter {
    constructor() {
        this.events = {}; // {event: [cb1, cb2]}, 存储事件和回调
    }
    subscribe(event, cb) {
        if (!this.events[event]) this.events[event] = []; // 初始化
        this.events[event].push(cb); // 订阅, 将回调存入事件对应数组
        return {
            unsubscribe: () => {
                // 取消订阅
                this.events[event] = this.events[event].filter((e) => e !== cb); // 过滤掉当前回调
            },
        };
    }

    emit(event, args = []) {
        if (!this.events[event]) return []; // 未订阅, 返回空数组
        return this.events[event].map((cb) => cb(...args)); // 执行回调
    }
}

这段代码定义了一个 EventEmiter 类,用于实现事件的订阅和触发功能。

EventEmitter 类的构造函数初始化了一个空的 events 对象,用于存储事件和回调的对应关系。

subscribe 方法用于订阅事件,接受两个参数:event 表示要订阅的事件名称,cb 表示事件触发时要执行的回调函数。如果 events 对象中没有对应的事件数组,会先进行初始化。然后将回调函数添加到事件对应的数组中,并返回一个包含 unsubscribe 方法的对象,用于取消订阅。unsubscribe 方法会将当前回调从事件的数组中过滤掉。

emit 方法用于触发事件,接受两个参数:event 表示要触发的事件名称,args 表示传递给回调函数的参数,默认为空数组。如果 events 对象中没有对应的事件数组,表示该事件未被订阅,直接返回空数组。否则,将事件对应的数组中的每一个回调函数都执行,并传入 args 参数,最后将执行结果组成的数组返回。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

leetcode----JavaScript 详情题解(4) 的相关文章

随机推荐

  • 人机博弈-吃子棋游戏(二)算气

    算法过程如下 输入计算棋气的起始子 检查我方棋子其周边的空白 并查看此空白是否已经计算过气了 如果没有计算过气加一 如果已经计算则略过 进而递归调用计算我方棋子上下左右子的气 最后算法会返回棋串的子数和气数 算法注释十分详尽 这个计算棋子气
  • linux中Shell历史命令记录文件的路径是什么

    Bash shell在 bash history 表示用户目录 文件中保存了500条使用过的命令 这样能使你输入使用过的长命令变得容易 每个在系统中拥有账号的用户在他的目录下都有一个 bash history 文件 bash shell应该
  • shell变量的设置规则

    1 变量设置规则 2 变量赋值与运算 1 变量赋值 name lbg 等号前后不能有空格 name Lebron James 变量值中有空格要用双引号 echo n a m e 用 name
  • PT100热敏电阻原理解析

    什么是PT100 PT100电阻是指铂热电阻 其中PT是指 铂 元素 100是指铂热电阻的特性在0 时 电阻值刚好是100 所以通常称它为PT100热电阻或铂电阻 PT100是中低温区最常用的一种温度检测元件 其导体的电阻值随温度的增加而成
  • 嵌入式Linux驱动开发(I2C专题)(一)

    一 I2C协议 1 1 硬件连接 I2C在硬件上的接法如下所示 主控芯片引出两条线SCL SDA线 在一条I2C总线上可以接很多I2C设备 1 2 IIC传输数据的格式 1 2 1 写操作 流程如下 主芯片要发出一个start信号 然后发出
  • android studio报错Out of memory: GC overhead limit exceeded. Please fix the project‘s Gradle settings

    项目场景 提示 这里简述项目相关背景 例如 项目场景 从gitee上克隆下来的项目从android studio中打开 问题描述 提示 这里描述项目中遇到的问题 例如 数据传输过程中数据不时出现丢失的情况 偶尔会丢失一部分数据 APP 中接
  • scratch关于克隆停止的问题

    克隆这个代码在scratch中用处挺大的 但是在用的过程中也会出现一些问题 比如说克隆在到达一定数量之后会停止继续克隆 这是由于克隆是有个数限制的 昨天测了一下克隆体的个数限制是300个左右 如何去测 就是当作为克隆体启动时 用一个初始化为
  • word 安装

    目录 概述 word 安装 1 下载安装 2 解压运行 3 卸载移除 4 安装部署 5 激活 亲测可用 记录一下 概述 Office Tool Plus 是一个强大的 Office 部署工具 可以很方便地部署 Office 它基于 Offi
  • 基于遗传算法的多目标优化算法(matlab实现)

    1 理论基础 1 1 多目标优化及Pareto最优解 多目标优化问题可以描述如下 其中 f x 为待优化的目标函数 x为待优化的变量 Ib和ub分别为变量x的下限和上限约束 Aeq x beq为变量x的线性等式约束 A x b为变量x的线性
  • HBuilder X3.1.22安装教程(非常详细)从零基础入门到精通,看完这一篇就够了(附安装包)

    软件下载 软件 HBuilder X 版本 3 1 22 语言 简体中文 大小 278 95M 安装环境 Win11 Win10 Win8 Win7 硬件要求 CPU 2 0GHz 内存 4G 或更高 下载通道 百度网盘丨下载链接 http
  • RPA机器人流程适用性评估的9个要素

    组织通常可以从以下几个方面来考虑RPA机器人流程的筛选 确保在RPA机器人流程自动化过程中产生最大投资回报率 ROI 如何去选择有影响力且易于RPA机器人自动化的流程 1 影响成本和收入的流程 最具影响力的流程 例如 如果定价规则不明确 报
  • 17. 实战:手把手通关某音乐平台热门评论

    目录 前言 链接在评论区 目的 保姆级思路 最后奉上完整代码 运行效果 前言 众所周知 某音乐平台的评论区金句频出 热门评论更是美不胜收 我们也想要批量获取这些信息来做信息分析 数据处理等工作 也可以陶冶自己的情操 一举多得 所以我们今天来
  • 蓝以中老师《高等代数》第02章:向量空间与矩阵,笔记

    蓝以中高代第二章笔记
  • Android开源图表库MPAndroidChart

    MPAndroidChart是一款基于Android的开源图表库 MPAndroidChart不仅可以在Android设备上绘制各种统计图表 而且可以对图表进行拖动和缩放操作 应用起来非常灵活 和前面介绍的AChartEngine相比 MP
  • AutoSAR 学习笔记2:AutoSAR架构

    1 应用层 ASW 2 运行时环境层 RTE RTE 是专门为应用软件 AutoSAR 软件组件和 或 AutoSAR 传感器 执行器组件 提供通信服务的层 在 RTE 之上 软件架构风格从 分层 转变为 组件风格 AutoSAR 软件组件
  • 模板模式

    1 模板模式的概念 在模板模式 Template Pattern 中 一个抽象类公开定义了执行它的方法的方式 模板 它的子类可以按需要重写方法实现 但调用将以抽象类中定义的方式进行 这种类型的设计模式属于行为型模式 2 模板模式的特点 子类
  • 从煎鸡蛋的角度理解编程的思维和流程,你适合学吗?

    其实很多门外人对编程都是懵懵懂懂的 我们可以先看一张图来理解一下 思维 就是程序员需要考虑到的各种需求 也就是我们想让计算机帮助我们实现什么 表达 就是计算机可以看懂的指令也就是0和1 那怎么将我们所想向计算机说出来 并且让它帮我们执行 就
  • server2008r2域控时间设置internet时间同步(备忘)

    windows server 2008 r2成为域控后 时间设置里的 internet时间就没有了 为了解决这个问题 用以下CMD命令可解决 w32tm config manualpeerlist time windows com sync
  • iOS 应用获取最上层全屏 Window 的正确方法

    有时候 我们需要将View添加到最上层的Window上 比如 弹出框 Loading等 经常有同学直接通过 UIApplication sharedApplication windows lastObject 来获取 这种方法是非常不严谨的
  • leetcode----JavaScript 详情题解(4)

    目录 2722 根据 ID 合并两个数组 2723 添加两个 Promise 对象 2724 排序方式 2725 间隔取消 2726 使用方法链的计算器 2727 判断对象是否为空 2624 蜗牛排序 2694 事件发射器 2722 根据