javascript中实现递归深拷贝并支持属性过滤需手动遍历对象、精确识别类型、用weakmap避免循环引用,并根据exclude/include/filter规则跳过指定属性,不依赖json方法以保留函数、symbol等类型。

JavaScript 中实现对象属性的递归深拷贝并同时支持属性过滤,核心在于手动遍历对象结构、判断类型、控制拷贝逻辑,并在遍历过程中根据规则跳过指定属性。不能依赖 JSON.parse(JSON.stringify(obj)),因为它会丢失函数、undefined、Symbol、Date、RegExp 等类型,也无法支持过滤。
递归深拷贝 + 属性过滤的基本思路
需要一个函数接收源对象、过滤规则(如黑名单数组、白名单数组、或自定义判断函数),然后递归处理每个键值对:对基本类型直接赋值;对引用类型(Object、Array、Date、RegExp 等)分别构造新实例并递归处理其内容;遇到需过滤的键则跳过。
- 区分数据类型:用
Object.prototype.toString.call(value)精确识别 Array、Date、RegExp、Map、Set 等 - 支持多种过滤方式:比如传入
exclude: ['password', 'token']或include: ['id', 'name'],或更灵活的filter: (key, value, path) => boolean - 避免循环引用:用 WeakMap 记录已拷贝的对象,遇到重复引用时直接返回缓存结果
支持过滤的深拷贝函数示例
以下是一个兼顾健壮性与可扩展性的实现:
function deepCloneWithFilter(obj, options = {}) {
const { exclude = [], include, filter } = options;
const seen = new WeakMap();
function clone(item, path = []) {
if (item === null || typeof item !== 'object') return item;
if (seen.has(item)) return seen.get(item);
const type = Object.prototype.toString.call(item);
let result;
// 处理数组
if (type === '[object Array]') {
result = [];
seen.set(item, result);
for (let i = 0; i {
if (!shouldSkip(`Map.${key}`, val, key, [...path, 'Map', key])) {
result.set(key, clone(val, [...path, 'Map', key]));
}
});
return result;
}
if (type === '[object Set]') {
result = new Set();
seen.set(item, result);
item.forEach(val => {
if (!shouldSkip('Set.item', val, 'item', [...path, 'Set'])) {
result.add(clone(val, [...path, 'Set']));
}
});
return result;
}
// 默认按浅拷贝处理(如 Buffer、TypedArray 等可自行扩展)
return item;
}
function shouldSkip(key, value, keyOrIndex, path) {
if (typeof filter === 'function') {
return !filter(key, value, keyOrIndex, path);
}
if (Array.isArray(include)) {
return !include.includes(key);
}
if (Array.isArray(exclude)) {
return exclude.includes(key);
}
return false;
}
return clone(obj);
}
常用调用方式
可根据场景选择不同过滤策略:
- 排除敏感字段:
deepCloneWithFilter(user, { exclude: ['password', 'sessionToken'] }) - 只保留指定字段:
deepCloneWithFilter(user, { include: ['id', 'name', 'email'] }) - 动态条件过滤(例如忽略 undefined 和空字符串):
deepCloneWithFilter(data, { filter: (k, v) => v != null && v !== '' }) - 路径感知过滤(如跳过所有嵌套的
_meta字段):filter: (k, v, _, path) => k !== '_meta'
注意事项与边界情况
实际使用中需留意几个易错点:
-
Symbol 键不会被
for...in或Object.keys()遍历,若需支持,应改用Reflect.ownKeys()并单独处理 -
不可枚举属性(如
Object.defineProperty设置的)默认不拷贝,如需保留,需显式调用Object.getOwnPropertyDescriptors() - Function、Error、Promise 等通常不深拷贝,多数场景下直接引用或忽略更合理;若必须复制,需额外逻辑(但语义可能失效)
- 性能敏感场景慎用深层递归,超深嵌套对象建议加最大深度限制或改用栈模拟递归
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











