深度复制物体的角度

AngularJS 使用 angular.copy()来深度复制对象和数组。

Angular 也有类似的东西吗?

102888 次浏览

This question isn't a duplicate of How can I use angular.copy in angular 2 because the OP is asking about deep copying objects. The linked answer recommends Object.assign() which doesn't make a deep copy.

Actually, using Angular2 doesn't restrict you from using other libraries like jQuery for deep copying objects with their $.clone() function or lodash with _.cloneDeep().

The most common libraries have their typings available via typings CLI tools so even when transpiling from TypeScript you can seamlessly use anything you want.

Also see: What is the most efficient way to deep clone an object in JavaScript?

Another option is to implement your own function:

/**
* Returns a deep copy of the object
*/
public static deepCopy(oldObj: any) {
var newObj = oldObj;
if (oldObj && typeof oldObj === "object") {
if (oldObj instanceof Date) {
return new Date(oldObj.getTime());
}
newObj = Object.prototype.toString.call(oldObj) === "[object Array]" ? [] : {};
for (var i in oldObj) {
newObj[i] = this.deepCopy(oldObj[i]);
}
}
return newObj;
}

You can also use:

JSON.parse(JSON.stringify(Object))

if it's on your scope, it's in every Angular component, directive, etc. and it's also on every node environment.

Unless you have a circular reference, it should work and will effectively dissociate your variable reference to the original object.

You can deep copy an object in Angular by using lodash's cloneDeep method:

Install lodash with yarn add lodash or npm install lodash.

In your component, import cloneDeep and use it:

import * as cloneDeep from 'lodash/cloneDeep';
...
clonedObject = cloneDeep(originalObject);

It's only 18kb added to your build, well worth for the benefits.

I've also written an article here, if you need more insight on why using lodash's cloneDeep.

If the source is an array of objects, using map:

let cloned = source.map(x => Object.assign({}, x));

OR

let cloned = source.map((x) => {
return { ...x };
});
Create helper class with name deepCopy.ts


/*
* DeepCopy class helps to copy an Original Array or an Object without impacting on original data
*/


export class DeepCopy {


static copy(data: any) {
let node;
if (Array.isArray(data)) {
node = data.length > 0 ? data.slice(0) : [];
node.forEach((e, i) => {
if (
(typeof e === 'object' && e !== {}) ||
(Array.isArray(e) && e.length > 0)
) {
node[i] = DeepCopy.copy(e);
}
});
} else if (data && typeof data === 'object') {
node = data instanceof Date ? data : Object.assign({}, data);
Object.keys(node).forEach((key) => {
if (
(typeof node[key] === 'object' && node[key] !== {}) ||
(Array.isArray(node[key]) && node[key].length > 0)
) {
node[key] = DeepCopy.copy(node[key]);
}
});
} else {
node = data;
}
return node;
}
}

Import deepCopy file where ever you required and use as below code DeepCopy.copy(arg); , Here arg would either object or array which you want

I have created a very simple function in typescript which accepts all possible inputs and gives the deep cloned copy of that object.

Hope it will help somebody.

  public deepCopy(obj) {


var clonedObject: any;


if (obj instanceof Array) {
var itemArray = Object.assign([], obj);
clonedObject = itemArray;


for (var j = 0; j < clonedObject.length; j++) {
clonedObject[j] = this.deepCopy(clonedObject[j]);
}


return clonedObject;
}
else if (typeof obj === 'number' || typeof obj == 'string') {
return obj
}
else {




var item = Object.assign({}, obj);
clonedObject = item;


let allKeys = Object.keys(clonedObject);


for (var i = 0; i < allKeys.length; i++) {
if (clonedObject[allKeys[i]] instanceof Array) {
//If the calue is Array
clonedObject[allKeys[i]] = this.deepCopy(clonedObject[allKeys[i]]);
}
else if (clonedObject[allKeys[i]] instanceof Date) {
clonedObject[allKeys[i]] = new Date(clonedObject[allKeys[i]].valueOf());
}
else if (clonedObject[allKeys[i]] instanceof Object){
//if the value is JOBJECT.
clonedObject[allKeys[i]] = this.deepCopy(clonedObject[allKeys[i]]);
}
}
return clonedObject;
}




}

I am faced with the problem of deep copying. angular.copy({}, factory) and angular.extend({}, factory) helps well for array or hashes objects, but when copying an object a calass, sometimes there may be problems with connected dependencies. I solved this problem so:

 copyFactory = (() ->
resource = ->
resource.__super__.constructor.apply this, arguments
return
this.extendTo resource
resource
).call(factory)

Some modifications for KrishnamrajuK's answer

export class DeepCopy {
static copy(data: any, objMap?: WeakMap<any, any>) {
if (!objMap) {
// Map for handle recursive objects
objMap = new WeakMap();
}


// recursion wrapper
const deeper = value => {
if (value && typeof value === 'object') {
return DeepCopy.copy(value, objMap);
}
return value;
};


// Array value
if (Array.isArray(data)) return data.map(deeper);


// Object value
if (data && typeof data === 'object') {
// Same object seen earlier
if (objMap.has(data)) return objMap.get(data);
// Date object
if (data instanceof Date) {
const result = new Date(data.valueOf());
objMap.set(data, result);
return result;
}
// Use original prototype
const node = Object.create(Object.getPrototypeOf(data));
// Save object to map before recursion
objMap.set(data, node);
for (const [key, value] of Object.entries(data)) {
node[key] = deeper(value);
}
return node;
}
// Scalar value
return data;
}
}


You can try this: https://www.npmjs.com/package/ngx-scv-util

I faced the same issue and created an npm package for the same. I know its an overhead just for this functionality :) . I plan to add more to this package over time. This one has been tested on 10.1.6 version of Angular. It should definitely work with versions higher than 10.1.6. Usage details are mentioned on the npm package page.

Some of the answers here rely on lodash which can cause issues when imported in angular 10+ with a warning message like the following:

WARNING in xxxxx.ts depends on lodash/cloneDeep. CommonJS or AMD dependencies can cause optimization bailouts.

Other answers use parsing via JSON or try to roll their own implementation. Not to roll ours, we use clone: a lightweight clone utility with limited dependencies.

In order to use clone, you just need to install these two packages:

npm install clone
npm install --save-dev @types/clone

The create a service (the service is not mandatory, but I prefer this approach) that uses the clone API:

import { Injectable } from '@angular/core';
import * as clone from 'clone';


@Injectable()
export class ObjectCloneService {
public cloneObject<T>(value: T): T {
return clone<T>(value);
}
}

Remember to add the service to your module.

To use cloneDeep without any "CommonJS or AMD dependencies can cause optimization bailouts.", you can use it like this:

npm i lodash-es --save

Then simply in any component:

import { cloneDeep } from 'lodash-es';
// ...
let a = cloneDeep(b);