通过复制所有自己的属性克隆任何javascript对象

通过复制所有自己的属性克隆任何javascript对象,javascript,Javascript,如果我想克隆任何javascript对象(不为null),我想我可以使用object.getOwnPropertyNames将它自己的所有属性(可枚举和不可枚举)复制到一个新的空对象上 但是我注意到Dojo toolkit()提供的一个深度克隆函数示例将RegExp、Date和Node对象视为特例,而lodash.cloneDeep还有很多逻辑,比简单地复制属性复杂得多,包括有自己的一些特殊情况,显然不支持所有类型的对象:() 为什么仅仅复制对象属性是不够的?除了属性之外,javascript对

如果我想克隆任何javascript对象(不为null),我想我可以使用object.getOwnPropertyNames将它自己的所有属性(可枚举和不可枚举)复制到一个新的空对象上

但是我注意到Dojo toolkit()提供的一个深度克隆函数示例将RegExp、Date和Node对象视为特例,而lodash.cloneDeep还有很多逻辑,比简单地复制属性复杂得多,包括有自己的一些特殊情况,显然不支持所有类型的对象:()

为什么仅仅复制对象属性是不够的?除了属性之外,javascript对象还有什么我不知道的

编辑:说清楚,我说的是深度克隆对象。很抱歉造成混淆。

大多数本机对象(如您所述-我不知道它们的正确命名;可能是内置的?)被视为“简单”:按属性复制日期对象属性没有意义。同时,它们在某种程度上都是可变的

let a = {test: new Date(1)}; // test === Thu Jan 01 1970 00:00:00GMT
let copy_a = {test: a.test}; // looks like cloned
a.test.setDate(12); // let's mutate original date
console.log(copy_a.test); // Thu Jan 12 1970 00:00:00GMT ooops modified as well

因此,您要么显式地处理这些异常(特殊情况),要么在某些情况下承担副作用的风险。

如果顶级属性都是值对象,如字符串和数字,则仅复制顶级属性就可以克隆对象。如果存在任何引用对象,如日期、数组或其他对象,则您所做的只是将引用从一个对象复制到另一个对象。如果更改克隆上的引用对象,则会更改原始对象

请看一下我的克隆函数

如果它是一个数组,它将克隆数组中的每个项目;如果它是一个日期,它将在同一时间创建一个新日期;如果它是一个对象,它将克隆每个属性;如果它只是复制属性,它将克隆每个属性

克隆对象现在可以变异,而不必担心它对原始对象的影响

const clone=obj=>
数组.isArray(obj)
? 对象映射(项=>克隆(项))
:obj instanceof Date
? 新日期(obj.getTime())
:(对象的类型===‘对象’&&obj
? Object.getOwnPropertyNames(obj).reduce((o,prop)=>({…o[prop]:克隆(obj[prop]),{})
:obj;
让original={prop1:“original”,objProp:{prop1:“original”};
让我们复制={…原件};
设clonedObj=克隆(原始);
clonedObj.prop1=“已更改”;
clonedObj.objProp.prop1=“已更改”;
log(`Original objects属性为'${Original.prop1}'和'${Original.objProp.prop1}');
sowncopy.prop1=“已更改”;
snowncopy.objProp.prop1=“已更改”;

log(`Original objects属性为'${Original.prop1}'和'${Original.objProp.prop1}')在JS中克隆对象的最简单方法是使用
..
扩展操作符

假设您有这个对象:

const object={foo:1,bar:2}

要克隆它,您只需声明:

constobjectclone={…object}

这将在克隆上创建原始对象中存在的所有属性及其值

现在的问题是,如果您在其中嵌套了任何对象,则将通过引用制作副本。假设原始对象是以下对象:

constudent={studentID:1,tests:{test1:90,test2:95}

如果使用spread操作符(或object.assign,spread只是语法糖)创建该对象的副本,嵌套对象实际上将指向原始对象内的对象!所以重复这一点:

const studentClone={…student}

现在编辑克隆内嵌套对象的属性:

studentClone.tests.test1=80

这将更改克隆对象和原始对象中的值,因为嵌套对象实际上只是指向内存中的一个对象

现在,这些实用程序,如
\uuu0.cloneDeep
将要做的是,迭代要克隆的对象中的所有内部对象,并重复该过程。从技术上讲,您可以自己做,但您无法轻松地在具有许多嵌套对象的对象上做。大概是这样的:

obj2.myField.setDate(obj2.myField.getDate() + 2);

console.log(obj1.myField);   // Result =====>  Wed Sep 19 2018 00:00:00 GMT+0430
constudentclone={…studentClone,测试:{…studentClone.tests}}

这将创建新对象,而不会出现引用问题

希望这有帮助

编辑:当然,只需添加对象,对象扩展将仅适用于原型对象。每个实例化对象(如数组、日期对象等)都有自己的克隆方式

同样,可以通过
[…array]
复制数组。它在引用方面遵循相同的规则。对于日期,只需将原始日期对象再次传递到日期构造函数:

const clonedDate=新日期(日期)


这就是第三方实用程序的用武之地,因为它们通常会处理大多数用例。

javascript中的对象包括字段和函数,每个字段都可以是另一个对象(如日期类型)。如果复制日期字段,则它将是引用类型分配。 例如:

现在,如果我们更改“obj2.myField”如下:

obj2.myField.setDate(obj2.myField.getDate() + 2);

console.log(obj1.myField);   // Result =====>  Wed Sep 19 2018 00:00:00 GMT+0430
如您所见,obj1和obj2仍然是链接的

复制日期字段的正确方法:

obj2.myField = new Date(obj1.myField.getTime());
很好地解释了克隆普通JavaScript对象的两个问题:原型属性和循环引用。但是为了回答您关于某些内置类型的问题,TL;DR的答案是有一些“引擎盖下”属性,您无法通过编程访问这些属性

考虑:

let foo = [1, 2];
let bar = {};
Object.assign(bar, foo);
Object.setPrototypeOf(bar, foo.constructor.prototype); // aka Array.prototype
bar[0]; // 1
bar instanceof Array; // true
bar.map(x => x + 1); // [] ????
空数组?为什么?只是为了确保我们没有疯

foo.map(x => x + 1); // [2, 3]
map
(以及其他数组方法)无法工作的原因在于数组不仅仅是一个对象:它具有内部插槽属性,用于放置在其中的对象
 Object.prototype.toString.call(true);   // [object Boolean]
 Object.prototype.toString.call(3);      // [object Number]
 Object.prototype.toString.call({});     // [object Object]
 Object.prototype.toString.call([]);     // [object Array]
 Object.prototype.toString.call(null);   // [object Null]
 Object.prototype.toString.call(/\w/);   // [object RegExp]
 Object.prototype.toString.call(JSON);   // [object JSON]
 Object.prototype.toString.call(Math);   // [object Math]
 Object.prototype.toString.call(foo); // [object Array]
 Object.prototype.toString.call(bar); // [object Object] Doh!