当前位置: 首页 > news >正文

JavaScript的常用数组API原理

1-JavaScript forEach 方法原理解析

基本实现原理

forEach 的核心是一个高阶函数,其伪代码实现如下:

Array.prototype.forEach = function(callback, thisArg) {// 1. 基础校验if (this == null) {throw new TypeError('this is null or not defined');}if (typeof callback !== 'function') {throw new TypeError(callback + ' is not a function');}// 2. 获取数组数据const O = Object(this);const len = O.length >>> 0; // 转换为无符号32位整数// 3. 遍历执行let k = 0;while (k < len) {if (k in O) { // 检查是否存在该索引(处理稀疏数组)callback.call(thisArg, O[k], k, O);}k++;}
};

关键特性解析

1. 执行机制

  • 遍历顺序:严格按索引升序执行(0 → length-1)
  • 空位处理:稀疏数组中空元素(empty)不会触发回调
    const sparse = [1,,3];
    sparse.forEach((item, i) => {console.log(i, item); // 输出: 0 1 → 2 3(跳过空位)
    });
    

2. 中断控制

  • 不可中断:无法通过常规手段终止遍历(区别于 for 循环)
  • 变通方案
    const arr = [1,2,3,4,5];
    arr.forEach((item) => {if (item === 3) throw new Error('break'); // 不推荐console.log(item);
    });
    // 更推荐使用 some/every 代替:
    arr.some(item => {if (item === 3) return true; // 返回true中断console.log(item);
    });
    

3. 上下文绑定

  • 可通过第二参数绑定 this
    class Counter {constructor() {this.count = 0;}increment(arr) {arr.forEach(function(item) {this.count += item;}, this); // 绑定当前实例}
    }
    

与普通 for 循环对比

特性forEachfor 循环
语法复杂度声明式,更简洁命令式,需手动控制
性能稍慢(函数调用开销)更快
中断能力不可中断break 中断
异步支持无法等待异步操作可配合 async/await
空元素处理自动跳过需要手动判断

底层实现示例

V8 引擎中的关键实现步骤:

  1. 检查数组是否被修改(写时复制保护)
  2. 处理稀疏数组的快速路径
  3. 生成优化后的机器代码(TurboFan 编译器)

最佳实践场景

适用场景

  • 纯遍历操作(无返回值需求)
  • 需要简洁的链式调用时
    data.filter(...).map(...).forEach(renderItem);
    

不适用场景

  • 需要提前终止的遍历
  • 性能敏感的密集循环
  • 依赖返回值的操作(应使用 map

常见误区

// 错误1:试图获取返回值
const result = [1,2,3].forEach(x => x*2); // undefined// 错误2:在异步函数中使用
async function process(array) {array.forEach(async (item) => {await doSomething(item); // 不会等待});console.log('Done'); // 会先执行
}

扩展知识

现代 JavaScript 引擎会对 forEach 做以下优化:

  • 内联回调函数(Inline Caching)
  • 生成类型特化代码(TurboFan)
  • 跳过数组边界检查(Fast Path)

建议在性能关键路径使用 for 循环,其他场景优先考虑 forEach 的代码可读性。

2-JavaScript reduce() 方法深度解析

reduce() 是 JavaScript 数组中最强大的高阶函数之一,它通过迭代处理数组元素,最终将数组"缩减"为单个值。下面我将从多个角度详细解析它的工作原理。

基础语法

array.reduce(callback(accumulator, currentValue, index, array), initialValue)

核心参数解析

参数说明
accumulator累积器,存储每次回调的返回值(或初始值)
currentValue当前处理的数组元素
index当前元素的索引(可选)
array调用reduce的原始数组(可选)
initialValue初始值(可选),如果不提供则使用数组第一个元素作为初始accumulator值

执行过程详解

1. 无初始值的情况

[1, 2, 3].reduce((acc, cur) => acc + cur)

执行步骤:

  1. 第一次调用:acc = 1 (数组第一个元素), cur = 2
  2. 第二次调用:acc = 1+2 = 3, cur = 3
  3. 返回最终结果:3 + 3 = 6

2. 有初始值的情况

[1, 2, 3].reduce((acc, cur) => acc + cur, 10)

执行步骤:

  1. 第一次调用:acc = 10 (初始值), cur = 1
  2. 第二次调用:acc = 10+1 = 11, cur = 2
  3. 第三次调用:acc = 11+2 = 13, cur = 3
  4. 返回最终结果:13 + 3 = 16

底层实现原理

Array.prototype.reduce = function(callback, initialValue) {if (this == null) {throw new TypeError('Array.prototype.reduce called on null or undefined');}if (typeof callback !== 'function') {throw new TypeError(callback + ' is not a function');}const O = Object(this);const len = O.length >>> 0;let k = 0;let accumulator;// 处理初始值if (arguments.length >= 2) {accumulator = initialValue;} else {// 无初始值时,跳过空位找到第一个有效元素while (k < len && !(k in O)) {k++;}if (k >= len) {throw new TypeError('Reduce of empty array with no initial value');}accumulator = O[k++];}// 迭代处理while (k < len) {if (k in O) {accumulator = callback.call(undefined, accumulator, O[k], k, O);}k++;}return accumulator;
};

高级应用场景

1. 多维数组扁平化

const nested = [[1, 2], [3, 4], [5, 6]];
const flat = nested.reduce((acc, cur) => acc.concat(cur), []);
// 结果: [1, 2, 3, 4, 5, 6]
初始值: []
↓ 合并 [1,2]
[1, 2]
↓ 合并 [3,4]
[1, 2, 3, 4]
↓ 合并 [5,6]
[1, 2, 3, 4, 5, 6]//conca方法:
const arr1 = [1, 2];
const arr2 = [3, 4];
const obj = {x: 5};const result = arr1.concat(arr2, obj, 5);
// 结果: [1, 2, 3, 4, {x: 5}, 5]
1-与展开运算符对比
特性concat[...arr]
参数处理自动展开一层数组完全展开所有可迭代对象
非数组参数直接作为元素添加必须显式展开
性能稍快(引擎优化)稍慢
可读性链式调用更流畅直观但嵌套复杂时难阅读
2-等价实现对比:
  1. 使用展开运算符

    nested.reduce((acc, cur) => [...acc, ...cur], []);
    
  2. 使用 flat 方法(ES2019):

    nested.flat();
    
  3. 递归实现(处理任意嵌套):

    function flatten(arr) {return arr.reduce((acc, cur) => acc.concat(Array.isArray(cur) ? flatten(cur) : cur), []);
    }
    

2. 按属性分组

const people = [{name: 'Alice', age: 21},{name: 'Bob', age: 21},{name: 'Charlie', age: 25}
];const grouped = people.reduce((acc, person) => {const age = person.age;if (!acc[age]) acc[age] = [];//检查 acc[21] 是否存在 → 不存在acc[age].push(person);//将 Alice 对象推入数组: acc[21] = [{ name: 'Alice', age: 21 }]return acc;
}, {});
/*
结果: {21: [{name: 'Alice', age: 21}, {name: 'Bob', age: 21}],25: [{name: 'Charlie', age: 25}]
}
*/

3. 函数管道组合

const pipe = (...funcs) => initialValue => funcs.reduce((acc, fn) => fn(acc), initialValue);const add5 = x => x + 5;
const double = x => x * 2;
const square = x => x * x;const transform = pipe(add5, double, square);
transform(2); // (((2 + 5) * 2) ^ 2) = 196  pipe 是从左到右执行
初始值: 2add5(2)7double(7)14square(14)196

性能与注意事项

  1. 初始值建议:总是提供初始值可以避免空数组错误
  2. 纯函数原则:回调函数应该是纯函数,不修改原数组
  3. 性能比较:在V8引擎中,reduce通常比for循环慢约30-40%
  4. 稀疏数组:会跳过空位(与map/filter等行为一致)

常见错误

// 错误1:无初始值且空数组
[].reduce((acc, cur) => acc + cur); // TypeError// 错误2:忘记返回accumulator
[1,2,3].reduce((acc, cur) => {acc + cur; // 没有return语句
}, 0); // 返回undefined// 错误3:修改原数组
[1,2,3].reduce((acc, cur, index, arr) => {arr.pop(); // 危险!修改正在迭代的数组return acc + cur;
}, 0);

reduce的强大之处在于它可以将复杂的迭代逻辑抽象为简单的函数组合,是函数式编程的重要工具。理解其原理后,可以优雅地解决许多数据转换问题。

3-JavaScript 数组方法深度解析:slice 和 flatMap

slice 方法详解

核心原理

slice() 方法创建一个新数组,包含从开始到结束(不包括结束)的原数组元素的浅拷贝。

Array.prototype.slice = function(start, end) {const result = [];const len = this.length;// 处理start参数let relativeStart = start >> 0; // 转换为整数relativeStart = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);// 处理end参数let relativeEnd = end === undefined ? len : end >> 0;relativeEnd = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);// 拷贝元素let count = Math.max(relativeEnd - relativeStart, 0);let k = relativeStart;while (count > 0) {if (k in this) {result.push(this[k]);} else {result.length++; // 处理稀疏数组}k++;count--;}return result;
};

关键特性

  1. 非破坏性操作:原数组保持不变
  2. 浅拷贝:对象/数组元素保持引用关系
    const objArr = [{a:1}, {b:2}];
    const sliced = objArr.slice(0,1);
    sliced[0].a = 99; // 会修改原数组中的对象
    
  3. 参数处理
    • 负数索引:从末尾开始计算
    • 超出范围:自动截断到有效范围
    • 省略end:截取到数组末尾

常见应用场景

  1. 数组拷贝

    const copy = arr.slice(); // 浅拷贝整个数组
    
  2. 类数组转换

    function example() {const args = Array.prototype.slice.call(arguments);
    }
    
  3. 分页处理

    function getPage(items, page, pageSize) {return items.slice((page-1)*pageSize, page*pageSize);
    }
    

flatMap 方法详解

核心原理

flatMap() 首先使用映射函数映射每个元素,然后将结果压缩一层(相当于 map() + flat(1))。

Array.prototype.flatMap = function(callback, thisArg) {return this.reduce((acc, current, index, array) => {const result = callback.call(thisArg, current, index, array);if (Array.isArray(result)) {for (const item of result) {acc.push(item);}} else {acc.push(result);}return acc;}, []);
};

关键特性

  1. 映射+扁平化:两步操作合二为一
  2. 只展开一层
    [1,2].flatMap(x => [[x*2]]) // [[2], [4]],不是[2,4]
    
  3. 性能优势:比分开调用 map()flat() 更高效

与 map + flat 对比

// 等价实现
const result1 = arr.map(x => x.split(' ')).flat();// flatMap实现
const result2 = arr.flatMap(x => x.split(' '));
特性map + flatflatMap
性能两次完整迭代单次迭代
内存生成中间数组无中间数组
可读性逻辑分开更简洁

高级应用

  1. 过滤+映射组合

    // 传统方式
    arr.filter(x => x !== null).map(x => x * 2);// 使用flatMap
    arr.flatMap(x => x === null ? [] : [x * 2]);
    
  2. 多值展开

    const data = [{id: 1, values: [10, 20]},{id: 2, values: [30, 40]}
    ];// 展开为 [10,20,30,40]
    const flattened = data.flatMap(item => item.values);
    
  3. 异步数据处理

    async function processArray(arr) {return Promise.all(arr.flatMap(async item => {const result = await processItem(item);return result.success ? [result.data] : [];}));
    }
    

性能优化建议

  1. slice优化

    • 对于大型数组,直接使用索引循环可能更快
    • 考虑使用 TypedArray 处理数值数据
  2. flatMap优化

    • 避免在回调中创建大型临时数组
    • 对于简单操作,for 循环可能更高效
  3. 现代替代方案

    // 使用展开运算符替代slice拷贝
    const copy = [...arr];// 使用Array.from处理类数组
    const args = Array.from(arguments);
    

理解这些方法的底层原理和特性,可以帮助你在不同场景下做出最优选择。

4-JavaScript splice() 方法深度解析

splice() 是 JavaScript 数组中最强大但也最危险的方法之一,它可以直接修改原数组。让我们全面解析它的工作原理和应用场景。

核心语法

array.splice(start[, deleteCount[, item1[, item2[, ...]]])

参数详解

参数描述
start修改开始的位置(从0开始)
deleteCount要移除的元素个数(可选,默认为0)
item1, item2, ...要添加进数组的元素(可选)

底层实现原理

Array.prototype._splice = function(start, deleteCount, ...items) {const len = this.length;// 1. 处理start参数let relativeStart = start >> 0; // 转换为整数relativeStart = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);// 2. 处理deleteCount参数const actualDeleteCount = Math.min(Math.max(deleteCount === undefined ? len : deleteCount >> 0, 0),len - relativeStart);// 3. 创建被删除元素的数组const deleted = new Array(actualDeleteCount);for (let i = 0; i < actualDeleteCount; i++) {const idx = relativeStart + i;if (idx in this) {deleted[i] = this[idx];}}// 4. 计算插入和删除的元素数量差const insertCount = items.length;const deltaCount = insertCount - actualDeleteCount;// 5. 调整数组长度if (deltaCount > 0) {// 向后移动元素腾出空间for (let i = len - 1; i >= relativeStart + actualDeleteCount; i--) {const from = i;const to = i + deltaCount;if (from in this) {this[to] = this[from];} else {delete this[to];}}} else if (deltaCount < 0) {// 向前移动元素填补空缺for (let i = relativeStart + actualDeleteCount; i < len; i++) {const from = i;const to = i + deltaCount;if (from in this) {this[to] = this[from];} else {delete this[to];}}}// 6. 插入新元素for (let i = 0; i < insertCount; i++) {this[relativeStart + i] = items[i];}// 7. 调整数组length属性this.length = len + deltaCount;return deleted;
};

执行过程示例

const arr = [1, 2, 3, 4, 5];
const removed = arr.splice(1, 2, 'a', 'b', 'c');

执行步骤分解:

  1. 初始数组: [1, 2, 3, 4, 5]
  2. 参数解析:
    • start = 1
    • deleteCount = 2
    • 要插入的元素: 'a', 'b', 'c'
  3. 删除操作:
    • 从索引1开始删除2个元素 → 删除23
    • removed 数组: [2, 3]
  4. 插入操作:
    • 在索引1处插入'a', 'b', 'c'
  5. 结果数组:
    • [1, 'a', 'b', 'c', 4, 5]
  6. 返回值:
    • [2, 3] (被删除的元素)

关键特性

  1. 破坏性操作:直接修改原数组
  2. 多功能性:可以同时实现删除和插入
  3. 返回值:总是返回被删除元素的数组
  4. 稀疏数组:会保留数组的稀疏性

常见使用模式

1. 删除元素

// 删除从索引2开始的3个元素
const arr = [1, 2, 3, 4, 5];
const deleted = arr.splice(2, 3); 
// arr变为[1, 2], deleted为[3, 4, 5]

2. 插入元素

// 在索引1处插入元素,不删除任何元素
const arr = [1, 2, 3];
arr.splice(1, 0, 'a', 'b');
// arr变为[1, 'a', 'b', 2, 3]

3. 替换元素

// 替换索引1处的2个元素
const arr = [1, 2, 3, 4];
const deleted = arr.splice(1, 2, 'a', 'b', 'c');
// arr变为[1, 'a', 'b', 'c', 4], deleted为[2, 3]

4. 清空数组

// 快速清空数组的替代方案
const arr = [1, 2, 3];
arr.splice(0);
// arr变为[], 返回[1, 2, 3]

性能考量

  1. 时间复杂度

    • 删除/插入操作: O(n) - 需要移动元素
    • 最坏情况: 在数组开头操作需要移动所有元素
  2. 优化建议

    • 批量操作优于多次小操作
    • 在数组末尾操作性能最好
    • 大型数组考虑使用非破坏性方法

特殊场景处理

1. 类数组对象

function example() {const args = Array.prototype.splice.call(arguments, 0);// 将arguments转换为真实数组
}

2. 负索引

const arr = [1, 2, 3, 4];
arr.splice(-2, 1); // 从倒数第2个位置删除1个元素
// arr变为[1, 2, 4]

3. 超出范围

const arr = [1, 2, 3];
arr.splice(5, 1, 'a'); // 开始位置超出长度,相当于在末尾添加
// arr变为[1, 2, 3, 'a']

安全替代方案

如果需要非破坏性操作,可以使用以下模式:

// 非破坏性删除
function safeDelete(arr, start, deleteCount) {return [...arr.slice(0, start), ...arr.slice(start + deleteCount)];
}// 非破坏性插入
function safeInsert(arr, start, ...items) {return [...arr.slice(0, start), ...items, ...arr.slice(start)];
}

splice() 是 JavaScript 数组操作中最灵活但也最危险的方法,合理使用可以大幅简化代码,但滥用可能导致难以追踪的副作用。理解其底层原理有助于在适当场景中发挥它的最大价值。

5-JavaScript 数组增删方法快速指南

1. push() - 末尾添加元素

作用:在数组末尾添加一个或多个元素,返回新长度

const fruits = ['apple', 'banana'];
const newLength = fruits.push('orange', 'pear');
console.log(fruits); // ['apple', 'banana', 'orange', 'pear']
console.log(newLength); // 4

2. pop() - 末尾删除元素

作用:删除并返回数组的最后一个元素

const fruits = ['apple', 'banana', 'orange'];
const last = fruits.pop();
console.log(fruits); // ['apple', 'banana']
console.log(last); // 'orange'

3. unshift() - 开头添加元素

作用:在数组开头添加一个或多个元素,返回新长度

const nums = [2, 3];
const newLength = nums.unshift(0, 1);
console.log(nums); // [0, 1, 2, 3]
console.log(newLength); // 4

4. shift() - 开头删除元素

作用:删除并返回数组的第一个元素

const nums = [1, 2, 3];
const first = nums.shift();
console.log(nums); // [2, 3]
console.log(first); // 1

5. splice() - 任意位置增删(补充)

作用:在指定位置删除/添加元素

const letters = ['a', 'b', 'c', 'd'];
// 从索引1开始删除1个元素,添加'x'和'y'
const removed = letters.splice(1, 1, 'x', 'y');
console.log(letters); // ['a', 'x', 'y', 'c', 'd']
console.log(removed); // ['b']

对比表格

方法作用位置修改方向返回值时间复杂度
push()末尾添加新长度O(1)
pop()末尾删除被删除元素O(1)
unshift()开头添加新长度O(n)
shift()开头删除被删除元素O(n)
splice()任意位置增删被删除元素的数组O(n)

使用场景示例

  1. 实现队列(先进先出)

    const queue = [];
    // 入队
    queue.push('任务1'); 
    queue.push('任务2');
    // 出队
    const task = queue.shift(); // '任务1'
    
  2. 实现栈(后进先出)

    const stack = [];
    // 入栈
    stack.push('页面1');
    stack.push('页面2');
    // 出栈
    const page = stack.pop(); // '页面2'
    
  3. 数组开头插入新数据

    const logEntries = [];
    // 新日志总是放在最前面
    logEntries.unshift('2023-10-01: 系统启动');
    logEntries.unshift('2023-10-02: 用户登录');
    

这些方法都是直接修改原数组的破坏性方法,使用时需要注意对原数组的影响。

6-JavaScript 数组排序与反转方法

sort() - 数组排序

作用:对数组元素进行排序(默认按字符串Unicode码点排序),会改变原数组

基本用法

const fruits = ['banana', 'apple', 'pear', 'orange'];
fruits.sort();
console.log(fruits); // ['apple', 'banana', 'orange', 'pear'] (字母顺序)

数字排序

const nums = [10, 5, 40, 25];
nums.sort(); // 错误方式:[10, 25, 40, 5] (按字符串排序)
nums.sort((a, b) => a - b); // 正确方式:[5, 10, 25, 40] (升序)
nums.sort((a, b) => b - a); // [40, 25, 10, 5] (降序)

对象数组排序

const users = [{name: 'John', age: 25},{name: 'Alice', age: 30},{name: 'Bob', age: 20}
];// 按年龄升序
users.sort((a, b) => a.age - b.age);
/* 结果:
[{name: 'Bob', age: 20},{name: 'John', age: 25},{name: 'Alice', age: 30}
]
*/

reverse() - 数组反转

作用:反转数组中元素的顺序,会改变原数组

基本用法

const letters = ['a', 'b', 'c', 'd'];
letters.reverse();
console.log(letters); // ['d', 'c', 'b', 'a']

结合sort使用

const nums = [1, 5, 2, 4];
nums.sort().reverse(); // 先升序排序再反转 → [5, 4, 2, 1]

看到这里,有个小点:关于 sort() 方法的排序行为解析

1. 第一个例子:[10, 5, 40, 25].sort()

const nums = [10, 5, 40, 25];
console.log(nums.sort()); // 输出: [10, 25, 40, 5]

原因

  • 当直接调用 sort() 不带比较函数时,所有元素都会被隐式转换为字符串
  • 然后按照 Unicode 码点顺序排序
  • 比较过程实际是:
    • “10” < “25” (true)
    • “25” < “40” (true)
    • “40” < “5” (false,因为 “4” 的码点小于 “5”)

2. 第二个例子:[1, 5, 2, 4].sort()

const nums1 = [1, 5, 2, 4];
console.log(nums1.sort()); // 输出: [1, 2, 4, 5]

看似"正确"排序的原因

  • 当数字都是个位数时:
    • 字符串表示和数值顺序一致
    • “1” < “2” < “4” < “5” 与 1 < 2 < 4 < 5 结果相同
  • 这其实是巧合,本质上仍然是字符串比较

关键结论

  1. sort() 的默认行为

    • 总是将元素转为字符串
    • 按字符的 Unicode 码点排序
    • 不是按数值大小排序
  2. 为什么看起来有时"正确"

    // 会"正确"排序(因为是巧合)
    [1, 2, 3].sort() // [1, 2, 3]// 不会正确排序(真实字符串比较)
    [1, 2, 10].sort() // [1, 10, 2]
    
  3. 正确的数字排序方式

    // 升序
    [10, 5, 40, 25].sort((a, b) => a - b); // [5, 10, 25, 40]// 降序
    [10, 5, 40, 25].sort((a, b) => b - a); // [40, 25, 10, 5]
    

实际开发建议

  1. 永远不要依赖默认排序

    • 即使是 [1,2,3] 这样的简单数组
    • 明确传递比较函数
  2. 对象数组排序

    const users = [{name: 'John', age: 25},{name: 'Alice', age: 30},{name: 'Bob', age: 20}
    ];// 按年龄升序
    users.sort((a, b) => a.age - b.age);
    
  3. 字符串本地化排序

    const names = ['赵', '钱', '孙', '李'];
    names.sort((a, b) => a.localeCompare(b, 'zh'));
    

记住:sort() 的默认行为是字符串比较,数值排序必须显式提供比较函数。

对象数组反转

const items = [{id: 1, name: 'Pen'},{id: 2, name: 'Notebook'},{id: 3, name: 'Pencil'}
];items.reverse();
/* 结果:
[{id: 3, name: 'Pencil'},{id: 2, name: 'Notebook'},{id: 1, name: 'Pen'}
]
*/

对比表格(补充)

方法作用是否修改原数组返回值特点
sort()排序数组元素✅ 是排序后的数组默认按字符串Unicode排序
reverse()反转数组元素顺序✅ 是反转后的数组单纯反转,不进行排序

使用注意事项

  1. sort() 的特殊性

    • 默认排序可能不符合数字预期
    • 比较函数应返回负数、0或正数:
      arr.sort((a, b) => {if (a < b) return -1; // a排在b前if (a > b) return 1;  // b排在a前return 0;             // 保持顺序
      });
      
  2. 性能考虑

    • sort() 的时间复杂度通常是 O(n log n)
    • 大型数组排序可能影响性能
  3. 非破坏性替代方案

    // 创建新数组排序
    const sorted = [...arr].sort();// 创建新数组反转
    const reversed = [...arr].reverse();
    

相关文章:

  • jspm企业采购管理系统的设计与实现(源码+lw+部署文档+讲解),源码可白嫖!
  • 第四篇:[特殊字符] 深入理解MyBatis[特殊字符] 掌握MyBatis Generator ——入门与实战
  • AI 边缘计算盒子:开启智能物联新时代
  • Proteus 仿真51单片机-串口收发小窥
  • ES关系映射(数据库中的表结构)
  • MySQL数据库---修改密码以及设置密码永过期
  • 云渗透二(云主机攻防)
  • 搭建一个网站需要选择什么配置的服务器?
  • 服务器数据恢复—AIX小型机误删数据如何找回?
  • Mysql联表查询
  • C++实用函数:bind
  • AcWing 1583:PAT 计数 ← 中国电子学会青少年软件编程等级考试(2024年C++四级)
  • spark-shell基础知识
  • VMware 安装 win10 系统的教程
  • java是实现视频流Rtsp转Rtmp
  • 从工业到家居:电源方案的广泛应用 | 多领域解决方案
  • VMWare 16 PRO 安装 Rocky8 并部署 MySQL8
  • 低功耗数字工厂的新时代:安卓触摸一体机助力绿色高效管理
  • SAP ABAP BOM 抬头更改(BAPI:CSAP_MAT_BOM_MAINTAIN)
  • 20250415-vue-插槽-默认内容
  • 致敬劳动者!今年拟表彰2426名全国劳动模范和先进工作者
  • 普京:俄方积极对待任何和平倡议
  • 国家税务总局镇江市税务局原纪检组组长朱永凯接受审查调查
  • 全国登记在册民营企业超过5700万户,占企业总量92.3%
  • 科普|一名肿瘤医生眼中的肺癌诊疗变化:从谈癌色变到与癌共存
  • 儿童阅读空间、残疾人友好书店……上海黄浦如何打造城市书房