JavaScript中处理数组、对象等特殊类型的操作汇总
作者:慧一居士
JavaScript 提供了丰富的内置方法和技巧来处理数组、对象及其他特殊类型(如 null、undefined、日期等)。以下是常见的操作汇总,按类别整理并附示例代码:
一、数组操作
1.创建与初始化
let arr = []; // 空数组 let nums = [1, 2, 3]; // 直接赋值 let cloned = original.slice(); // 浅拷贝(也可用扩展运算符:[...original])
2.增删改查基础方法
| 功能 | 方法 | 示例 |
|---|---|---|
| 末尾添加元素 | push() | arr.push(5); → [1,2,3,5] |
| 开头插入元素 | unshift() | arr.unshift(0); → [0,1,2,3] |
| 删除末尾项 | pop() | let last = arr.pop(); |
| 删除首项 | shift() | let first = arr.shift(); |
| 查找索引 | indexOf(), lastIndexOf() | arr.indexOf(2); //=> 1 |
| 是否包含 | includes() (ES6+) | arr.includes(2); //=> true |
| 拼接数组 | concat() or spread operator (...) | arr.concat([4,5]); ↔ [...arr,4,5] |
| 截取子串 | slice(startIdx, endIdx) (不修改原数组), splice(start, deleteCount, addItems...) (会修改原数组) | slice(1,3)→返回新数组;splice(1,1,99)→替换第二个元素为99 |
3.高阶函数式编程
迭代类:
forEach((item, index, array) => {...}) → 无返回值,纯执行副作用
map() → 生成新数组,对每个元素进行处理后返回结果集
const doubled = nums.map(n => n * 2); // [2,4,6]
filter() → 根据条件筛选符合条件的项组成新数组
const evens = nums.filter(n => n % 2 === 0); // [2] if nums=[1,2,3]
reduce((accumulator, currentValue) => {}, initialValue) → 累加器模式,常用于求和/积等聚合计算
const sum = nums.reduce((sum, cur) => sum + cur, 0); // 1+2+3=6
some() / every() → 判断是否存在至少一个真值 / 是否全部为真
boolArray.some(Boolean); // 是否有至少一个true?
扁平化多维数组:
const deepNested = [1, [2, [3, 4]]];
const flatted = deepNested.flat(Infinity); // ES2019+: [1,2,3,4]
// 兼容旧环境可用递归实现:
function myFlat(arr) {
return arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? myFlat(val) : val), []);
}
4.去重 & 排序
// Set去重法(最简单高效) const uniqueSet = [...new Set(arr)]; // 或使用filter手动实现: const uniqueFiltered = arr.filter((elem, i, self) => self.indexOf(elem) === i); // 排序(默认按字符串Unicode码点排序): arr.sort((a, b) => a - b); // 升序数字排序 arr.sort((a, b) => b - a); // 降序数字排序 arr.sort((a, b) => String(b).localeCompare(String(a))); // 本地化字符串排序
5.其他实用工具方法
| 方法 | 作用 | 备注 |
|---|---|---|
| join(separator) | 将数组转为字符串并用分隔符合并 | ['a','b'].join('-') => 'a-b' |
| reverse() | 反转数组顺序(原地修改!) | 慎用!会改变原数组 |
| fill(value) | 填充整个数组为指定值 | arr.fill(0).toString(); |
| find() | 返回第一个满足条件的单项 | 类似some但返回元素本身而非bool |
| entries()/keys()/values() | 获取迭代器遍历键值对/键名/值 | 配合for…of循环使用 |
二、对象操作
1.增删改查属性
obj.newProp = "value"; // 新增或覆盖已有属性
delete obj.oldProp; // 删除属性
Object.defineProperty(obj, 'dynProp', { // 精准控制配置项(如只读、可枚举性等)
value: 42,
writable: false,
enumerable: true
});
2.合并多个对象
方式①:展开运算符 + Rest参数组合
const merged = { ...objA, ...objB }; // 后者优先级高于前者(冲突时覆盖)
方式②:Object.assign()
Object.assign({}, defaultConfig, userSettings); // 深度合并不可行!仅浅层覆盖
注意:两者均为浅拷贝,深层嵌套需递归处理。
3.遍历对象的键值对
现代语法推荐使用以下三种之一:
// for...in循环所有可枚举自有属性(包括继承自原型链上的!除非hasOwn检查)
for (const key in obj) {
if (obj.hasOwnProperty(key)) { /* 确保只处理自身属性 */ }
}
// Object.keys()/values()/entries()获取有序数组形式的结果
console.log(Object.keys(obj)); // ["name", "age"]
console.log(Object.entries(obj)); // [["name","John"], ["age",30]]
// Reflect API更安全地替代传统方法:
Reflect.ownKeys(obj); // 包括Symbol类型的所有自有成员
Reflect.get(targetObj, propName); // 替代 targetObj[propName]
4.检测存在性与类型判断
避免直接用 if (obj[prop]) 因为可能发生假值误判(如0、空字符串也是有效值),应该这样写:
if ('prop' in obj) { /* 存在于对象中(无论是否是自身还是原型链上的)*/ }
if (obj.hasOwnProperty('prop')) { /* 仅限于对象自身的属性 */ }
// ES6新增更简洁写法:
if (Object.prototype.hasOwnProperty.call(obj, 'prop')) { ... }
5.冻结对象防止修改
const frozenObj = Object.freeze(originalObj); // 使其不可变,尝试修改会静默失败(严格模式下抛出错误)
反向操作可用 Object.isFrozen(obj) 检测是否已被冻结。
三、特殊类型处理技巧
1.null vs undefined
区别:null代表有意设置的空引用;undefined表示变量从未赋值过。
安全访问嵌套结构(可选链操作符 ?.):
const street = user?.address?.street; // 如果中间某个环节不存在则返回undefined而不是报错
默认值兜底模式:
const name = user && user.nickname || 'Anonymous'; // 经典写法但有坑(见注¹) // 更可靠的方式: const displayName = user?.nickname ?? 'Anonymous'; // 空值合并运算符(Nullish Coalescing)只在左侧为null/undefined时生效
注¹:当用户存在但昵称恰好是false/0/""时,上面的经典写法会得到错误结果!因此推荐使用??代替||作为默认值回退逻辑。
2.日期与时间格式化
原生API较为繁琐,建议封装工具函数:
function formatDate(dateStr, formatTemplate = 'YYYY-MM-DD hh:mm:ss') {
const d = new Date(dateStr);
const padZero = (num) => num.toString().padStart(2, '0');
return formatTemplate
.replace('YYYY', d.getFullYear())
.replace('MM', padZero(d.getMonth() + 1))
.replace('DD', padZero(d.getDate()))
.replace('hh', padZero(d.getHours()))
.replace('mm', padZero(d.getMinutes()))
.replace('ss', padZero(d.getSeconds()));
}
// 使用示例:formatDate('2025-04-30T15:30:00Z'); → "2025-04-30 15:30:00"
高级场景推荐第三方库如 moment.js 或 date-fns。
3.JSON序列化陷阱应对
某些特殊值无法直接被JSON.stringify正常处理:
| 数据类型 | JSON输出结果 | 解决方案 |
|---|---|---|
| BigInt | TypeError | .toString()转换 |
| Function | undefined | 排除或转成函数名字符串 |
| Symbol | ignored | 同上 |
| Map/Set | converted to plain object | 自定义解析规则 |
| Circular Refs | fail with stack overflow | 手工打破循环引用 |
示例修复方案:
const dataWithCycle = { self: null };
dataWithCycle.self = dataWithCycle; // 形成环形引用
const safeData = JSON.parse(JSON.stringify(dataWithCycle, replacer));
function replacer(key, value) {
if (value === dataWithCycle) return '[Circular]'; // 特殊标记处理
return value;
}
四、综合实践建议
- 优先选用现代语法特性:如展开运算符、箭头函数、可选链、空值合并等,使代码更简洁易读。
- 警惕副作用:许多数组方法(如
splice,reverse,sort)会修改原数组,如需保持纯粹性应先复制一份再操作。 - 性能考量:大数据量时尽量避免在循环内部频繁调用高复杂度的方法(如多次调用
includes导致O(n²)复杂度),考虑改用Set进行存在性检测以降低复杂度至O(1)。 - 类型安全边际保护:即使JS是弱类型语言,也应尽量保证变量符合预期的类型范围,例如通过TypeScript强类型注解或运行时校验库(如io-ts)。
掌握这些核心技能后,你可以高效处理绝大多数JS开发中的数据处理需求。遇到复杂场景时,建议查阅MDN官方文档或源码实现原理。
到此这篇关于JavaScript中处理数组、对象等特殊类型的操作汇总的文章就介绍到这了,更多相关JavaScript处理数组与对象内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
