Bản sao sâu trong ES6 bằng cách sử dụng cú pháp lây lan


98

Tôi đang cố gắng tạo một phương pháp bản đồ sao chép sâu cho dự án Redux của mình sẽ hoạt động với các đối tượng thay vì mảng. Tôi đọc rằng trong Redux mỗi trạng thái không nên thay đổi bất kỳ điều gì trong các trạng thái trước đó.

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    output[key] = callback.call(this, {...object[key]});

    return output;
    }, {});
}

Nó hoạt động:

    return mapCopy(state, e => {

            if (e.id === action.id) {
                 e.title = 'new item';
            }

            return e;
        })

Tuy nhiên, nó không sao chép sâu các mục bên trong nên tôi cần phải chỉnh nó thành:

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    let newObject = {...object[key]};
    newObject.style = {...newObject.style};
    newObject.data = {...newObject.data};

    output[key] = callback.call(this, newObject);

    return output;
    }, {});
}

Điều này kém thanh lịch hơn vì nó yêu cầu biết đối tượng nào được chuyển qua. Có cách nào trong ES6 để sử dụng cú pháp lây lan để sao chép sâu một đối tượng không?



8
Đây là một vấn đề XY. Bạn không cần phải làm việc nhiều trên các thuộc tính sâu trong redux. thay vào đó, bạn chỉ nên tạo một trình thu gọn khác hoạt động trên lát con của hình dạng trạng thái và sau đó sử dụng combineReducersđể kết hợp hai (hoặc nhiều) với nhau. Nếu bạn sử dụng các kỹ thuật redux thành ngữ, vấn đề nhân bản sâu đối tượng của bạn sẽ biến mất.
Cảm ơn bạn

Câu trả lời:


71

Không có chức năng nào như vậy được tích hợp trong ES6. Tôi nghĩ bạn có một số lựa chọn tùy thuộc vào những gì bạn muốn làm.

Nếu bạn thực sự muốn sao chép sâu:

  1. Sử dụng thư viện. Ví dụ, lodash có một cloneDeepphương thức.
  2. Thực hiện chức năng nhân bản của riêng bạn.

Giải pháp thay thế cho vấn đề cụ thể của bạn (Không có bản sao sâu)

Tuy nhiên, tôi nghĩ, nếu bạn sẵn sàng thay đổi một vài điều, bạn có thể tiết kiệm cho mình một số công việc. Tôi giả sử bạn kiểm soát tất cả các trang web cuộc gọi đến chức năng của bạn.

  1. Chỉ định rằng tất cả các lệnh gọi lại được chuyển đến mapCopyphải trả về các đối tượng mới thay vì thay đổi đối tượng hiện có. Ví dụ:

    mapCopy(state, e => {
      if (e.id === action.id) {
        return Object.assign({}, e, {
          title: 'new item'
        });
      } else {  
        return e;
      }
    });

    Điều này sử dụng Object.assignđể tạo một đối tượng mới, đặt thuộc tính của eđối tượng mới đó, sau đó đặt tiêu đề mới cho đối tượng mới đó. Điều này có nghĩa là bạn không bao giờ thay đổi các đối tượng hiện có và chỉ tạo những đối tượng mới khi cần thiết.

  2. mapCopy bây giờ có thể thực sự đơn giản:

    export const mapCopy = (object, callback) => {
      return Object.keys(object).reduce(function (output, key) {
        output[key] = callback.call(this, object[key]);
        return output;
      }, {});
    }

Về cơ bản, mapCopylà tin tưởng người gọi của mình làm điều đúng đắn. Đây là lý do tại sao tôi nói điều này giả sử bạn kiểm soát tất cả các trang web cuộc gọi.


3
Object.assign không sao chép sâu các đối tượng. xem developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Object.assign () sao chép các giá trị thuộc tính. "Nếu giá trị nguồn là một tham chiếu đến một đối tượng, nó chỉ sao chép giá trị tham chiếu đó."
Greg Somers

Đúng. Đây là một giải pháp thay thế không liên quan đến sao chép sâu. Tôi sẽ cập nhật câu trả lời của mình để rõ ràng hơn về điều đó.
Frank Tan

101

Thay vào đó, hãy sử dụng cái này cho bản sao sâu

var newObject = JSON.parse(JSON.stringify(oldObject))

var oldObject = {
  name: 'A',
  address: {
    street: 'Station Road',
    city: 'Pune'
  }
}
var newObject = JSON.parse(JSON.stringify(oldObject));

newObject.address.city = 'Delhi';
console.log('newObject');
console.log(newObject);
console.log('oldObject');
console.log(oldObject);


63
Điều này chỉ hoạt động nếu bạn không cần sao chép các chức năng. JSON sẽ bỏ qua tất cả các chức năng để bạn không có chúng trong bản sao.
Noland

7
Bên cạnh chức năng, bạn sẽ có vấn đề với undefined và null sử dụng phương pháp này
James Heazlewood

2
Bạn cũng sẽ gặp vấn đề với bất kỳ lớp nào do người dùng xác định, vì chuỗi nguyên mẫu không được tuần tự hóa.
Patrick Roberts

8
Giải pháp của bạn bằng cách sử dụng tuần tự hóa JSON có một số vấn đề. Bằng cách này, bạn sẽ mất bất kỳ thuộc tính Javascript nào không có kiểu tương đương trong JSON, như Hàm hoặc Vô cực. Bất kỳ thuộc tính nào được gán cho undefined sẽ bị JSON.stringify bỏ qua, khiến chúng bị bỏ sót trên đối tượng nhân bản. Ngoài ra, một số đối tượng được chuyển đổi thành chuỗi, như Ngày, Đặt, Bản đồ và nhiều đối tượng khác.
Jonathan Brizio

2
Tôi đã gặp phải một cơn ác mộng kinh hoàng khi cố gắng tạo một bản sao thật của một mảng đối tượng - các đối tượng về cơ bản là giá trị dữ liệu, không có chức năng. Nếu đó là tất cả những gì bạn phải lo lắng, thì cách tiếp cận này hoạt động rất hiệu quả.
Charlie

29

Từ MDN

Lưu ý: Cú pháp Spread thực sự đi sâu một cấp trong khi sao chép một mảng. Do đó, nó có thể không phù hợp để sao chép mảng đa chiều như ví dụ sau cho thấy (nó giống với cú pháp Object.assign () và spread).

Cá nhân tôi khuyên bạn nên sử dụng hàm cloneDeep của Lodash để nhân bản đối tượng / mảng nhiều cấp.

Đây là một ví dụ hoạt động:

const arr1 = [{ 'a': 1 }];

const arr2 = [...arr1];

const arr3 = _.clone(arr1);

const arr4 = arr1.slice();

const arr5 = _.cloneDeep(arr1);

const arr6 = [...{...arr1}]; // a bit ugly syntax but it is working!


// first level
console.log(arr1 === arr2); // false
console.log(arr1 === arr3); // false
console.log(arr1 === arr4); // false
console.log(arr1 === arr5); // false
console.log(arr1 === arr6); // false

// second level
console.log(arr1[0] === arr2[0]); // true
console.log(arr1[0] === arr3[0]); // true
console.log(arr1[0] === arr4[0]); // true
console.log(arr1[0] === arr5[0]); // false
console.log(arr1[0] === arr6[0]); // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>


4
arr6 không làm việc cho tôi. Trong trình duyệt (chrome 59.0 hỗ trợ ES6, tôi nhận được Uncaught SyntaxError: Không mong đợi mã thông báo ... và trong nút 8.9.3 hỗ trợ ES7, tôi nhận được TypeError: undefined không phải là chức năng repl: 1: 22
Achi Even-dar

@ AchiEven-dar không thưa đức vua tại sao bạn gặp lỗi. Bạn có thể chạy mã này trực tiếp trong stackoverflow bằng cách nhấn vào nút màu xanh lam Run code snippetvà nó sẽ chạy chính xác.
Mina Luke

3
arr6 cũng không hoạt động với tôi. Trong trình duyệt - chrome 65
yehonatan yehezkel

17

Tôi thường sử dụng cái này:

function deepCopy(obj) {
    if(typeof obj !== 'object' || obj === null) {
        return obj;
    }

    if(obj instanceof Date) {
        return new Date(obj.getTime());
    }

    if(obj instanceof Array) {
        return obj.reduce((arr, item, i) => {
            arr[i] = deepCopy(item);
            return arr;
        }, []);
    }

    if(obj instanceof Object) {
        return Object.keys(obj).reduce((newObj, key) => {
            newObj[key] = deepCopy(obj[key]);
            return newObj;
        }, {})
    }
}

3
const a = {
  foods: {
    dinner: 'Pasta'
  }
}
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

Sử dụng JSON.stringifyJSON.parselà cách tốt nhất. Bởi vì bằng cách sử dụng toán tử spread, chúng ta sẽ không nhận được câu trả lời hiệu quả khi đối tượng json chứa một đối tượng khác bên trong nó. chúng ta cần xác định thủ công điều đó.


1
function deepclone(obj) {
    let newObj = {};

    if (typeof obj === 'object') {
        for (let key in obj) {
            let property = obj[key],
                type = typeof property;
            switch (type) {
                case 'object':
                    if( Object.prototype.toString.call( property ) === '[object Array]' ) {
                        newObj[key] = [];
                        for (let item of property) {
                            newObj[key].push(this.deepclone(item))
                        }
                    } else {
                        newObj[key] = deepclone(property);
                    }
                    break;
                default:
                    newObj[key] = property;
                    break;

            }
        }
        return newObj
    } else {
        return obj;
    }
}

1
// use: clone( <thing to copy> ) returns <new copy>
// untested use at own risk
function clone(o, m){
  // return non object values
  if('object' !==typeof o) return o
  // m: a map of old refs to new object refs to stop recursion
  if('object' !==typeof m || null ===m) m =new WeakMap()
  var n =m.get(o)
  if('undefined' !==typeof n) return n
  // shallow/leaf clone object
  var c =Object.getPrototypeOf(o).constructor
  // TODO: specialize copies for expected built in types i.e. Date etc
  switch(c) {
    // shouldn't be copied, keep reference
    case Boolean:
    case Error:
    case Function:
    case Number:
    case Promise:
    case String:
    case Symbol:
    case WeakMap:
    case WeakSet:
      n =o
      break;
    // array like/collection objects
    case Array:
      m.set(o, n =o.slice(0))
      // recursive copy for child objects
      n.forEach(function(v,i){
        if('object' ===typeof v) n[i] =clone(v, m)
      });
      break;
    case ArrayBuffer:
      m.set(o, n =o.slice(0))
      break;
    case DataView:
      m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.byteLength))
      break;
    case Map:
    case Set:
      m.set(o, n =new (c)(clone(Array.from(o.entries()), m)))
      break;
    case Int8Array:
    case Uint8Array:
    case Uint8ClampedArray:
    case Int16Array:
    case Uint16Array:
    case Int32Array:
    case Uint32Array:
    case Float32Array:
    case Float64Array:
      m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.length))
      break;
    // use built in copy constructor
    case Date:
    case RegExp:
      m.set(o, n =new (c)(o))
      break;
    // fallback generic object copy
    default:
      m.set(o, n =Object.assign(new (c)(), o))
      // recursive copy for child objects
      for(c in n) if('object' ===typeof n[c]) n[c] =clone(n[c], m)
  }
  return n
}

Nhận xét có trong mã cho những người tìm kiếm lời giải thích.
Wookies-Will-Code

1
const cloneData = (dataArray) => {
    newData= []
    dataArray.forEach((value) => {
        newData.push({...value})
    })
    return newData
}
  • a = [{name: "siva"}, {name: "siva1"}];
  • b = myCopy (a)
  • b === a // sai`

1

Bản thân tôi đã cập nhật những câu trả lời này vào ngày hôm trước, cố gắng tìm cách sao chép sâu các cấu trúc phức tạp, có thể bao gồm các liên kết đệ quy. Vì tôi không hài lòng với bất cứ điều gì được đề xuất trước đó, tôi đã tự thực hiện bánh xe này. Và nó hoạt động khá tốt. Hy vọng nó sẽ giúp một ai đó.

Ví dụ sử dụng:

OriginalStruct.deep_copy = deep_copy; // attach the function as a method

TheClone = OriginalStruct.deep_copy();

Vui lòng xem https://github.com/latitov/JS_DeepCopy để biết các ví dụ trực tiếp về cách sử dụng nó và deep_print () cũng có ở đó.

Nếu bạn cần nó nhanh chóng, đây là nguồn của hàm deep_copy ():

function deep_copy() {
    'use strict';   // required for undef test of 'this' below

    // Copyright (c) 2019, Leonid Titov, Mentions Highly Appreciated.

    var id_cnt = 1;
    var all_old_objects = {};
    var all_new_objects = {};
    var root_obj = this;

    if (root_obj === undefined) {
        console.log(`deep_copy() error: wrong call context`);
        return;
    }

    var new_obj = copy_obj(root_obj);

    for (var id in all_old_objects) {
        delete all_old_objects[id].__temp_id;
    }

    return new_obj;
    //

    function copy_obj(o) {
        var new_obj = {};
        if (o.__temp_id === undefined) {
            o.__temp_id = id_cnt;
            all_old_objects[id_cnt] = o;
            all_new_objects[id_cnt] = new_obj;
            id_cnt ++;

            for (var prop in o) {
                if (o[prop] instanceof Array) {
                    new_obj[prop] = copy_array(o[prop]);
                }
                else if (o[prop] instanceof Object) {
                    new_obj[prop] = copy_obj(o[prop]);
                }
                else if (prop === '__temp_id') {
                    continue;
                }
                else {
                    new_obj[prop] = o[prop];
                }
            }
        }
        else {
            new_obj = all_new_objects[o.__temp_id];
        }
        return new_obj;
    }
    function copy_array(a) {
        var new_array = [];
        if (a.__temp_id === undefined) {
            a.__temp_id = id_cnt;
            all_old_objects[id_cnt] = a;
            all_new_objects[id_cnt] = new_array;
            id_cnt ++;

            a.forEach((v,i) => {
                if (v instanceof Array) {
                    new_array[i] = copy_array(v);
                }
                else if (v instanceof Object) {
                    new_array[i] = copy_object(v);
                }
                else {
                    new_array[i] = v;
                }
            });
        }
        else {
            new_array = all_new_objects[a.__temp_id];
        }
        return new_array;
    }
}

Chúc mừng @!


1

Đây là thuật toán sao chép sâu của tôi.

const DeepClone = (obj) => {
     if(obj===null||typeof(obj)!=='object')return null;
    let newObj = { ...obj };

    for (let prop in obj) {
      if (
        typeof obj[prop] === "object" ||
        typeof obj[prop] === "function"
      ) {
        newObj[prop] = DeepClone(obj[prop]);
      }
    }

    return newObj;
  };

Bạn cũng cần kiểm tra xem liệu 'obj [prop]! == null' là typeof (null) cũng trả về 'object' hay không
Pramod Mali

0

Đây là hàm deepClone xử lý tất cả các kiểu dữ liệu nguyên thủy, mảng, đối tượng, hàm

function deepClone(obj){
	if(Array.isArray(obj)){
		var arr = [];
		for (var i = 0; i < obj.length; i++) {
			arr[i] = deepClone(obj[i]);
		}
		return arr;
	}

	if(typeof(obj) == "object"){
		var cloned = {};
		for(let key in obj){
			cloned[key] = deepClone(obj[key])
		}
		return cloned;	
	}
	return obj;
}

console.log( deepClone(1) )

console.log( deepClone('abc') )

console.log( deepClone([1,2]) )

console.log( deepClone({a: 'abc', b: 'def'}) )

console.log( deepClone({
  a: 'a',
  num: 123,
  func: function(){'hello'},
  arr: [[1,2,3,[4,5]], 'def'],
  obj: {
    one: {
      two: {
        three: 3
      }
    }
  }
}) ) 

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.