JS: неожиданное наследование через метод .push()

#javascript #object #inheritance

#javascript #объект #наследование

Вопрос:

Сейчас я разрабатываю какой-то игровой движок, работающий в canvas. У меня есть некоторые базовые, но теперь я хочу добавить родителей и дочерних элементов… Все работает нормально, пока я не попытаюсь добавить ссылку на дочерний объект к родительскому объекту. Это очень короткая часть моей проблемы:

 // Object that will be parent of obj1
var obj0 = {
  childs: [],
  someValue: 10,
  parent: null
}

// obj1 is defined from obj0 throught Object.assign(), so any change in
// obj1 will NOT reflect to obj0 (I added console.log() to show it)
var obj1 = Object.assign({}, obj0);
console.log("Expected result: false; Result: "   (obj0 == obj1));
obj1.someValue = 5;
console.log("Expected result: false; Result: "   (obj0.someValue == obj1.someValue));

// Now add obj0 as parent to obj1...
obj1.parent = obj0;

// ... and add obj1 as child to obj0 - I need to do this throught .parent
// (I can't directly do some change in obj1)
obj1.parent.childs.push(obj1);

// Everything seems be alright, but... If I look into obj1.childs...
console.log(obj1.childs)
// I see, that .push applied to all objects that are...  

Как вы можете видеть, .push() метод применяется ко всем объектам (в данном случае к обоим объектам).

Итак, мои вопросы:
1. Почему?
2. Как этого избежать?

Заранее благодарю вас…

Комментарии:

1. В результате Object.assign() вызова оба объекта получают копию одного и того же childs массива. Это «неглубокая» копия.

2. @Pointy Вы правы, спасибо.. Но, есть ли у вас какие-либо идеи, как этого избежать?

3. Ну, вам пришлось бы написать свой собственный код, чтобы скопировать один из ваших объектов в другой. По моему опыту, желание выполнить «глубокую копию» указывает на проблему архитектуры, и в общем случае глубокие копии объектов даже не всегда возможны.

4. @Явно Да… Я знаю, что создать точную копию объекта очень сложно, но если у меня там только пустой массив (и он будет пустым каждый раз, когда я создаю новый объект), могу ли я делать что-то такое obj1.childs = [] всякий раз, когда я создаю новый? Ссылки не будет, верно?

5. Object.assign не имеет ничего общего с наследованием. Ты имел в виду Object.create ?

Ответ №1:

После obj1 = Object.assign({}, obj0) оба obj0 и obj1 ссылаются на один и тот же childs массив. Таким образом, какую бы мутацию вы ни внесли в этот массив (например, с помощью push ), она будет видна через оба объекта.

Один из способов избавиться от таких нежелательных эффектов — создать конструктор (особенность JavaScript), возможно, даже используя class обозначение:

 class MyClass {
    constructor(value) {
        this.childs = [];
        this.someValue = value;
        this.parent = null;
    }
}

// Object that will be parent of obj1
var obj0 = new MyClass(10);

var obj1 = new MyClass(5);
console.log("Expected result: false; Result: "   (obj0 == obj1));
console.log("Expected result: false; Result: "   (obj0.someValue == obj1.someValue));

// Now add obj0 as parent to obj1...
obj1.parent = obj0;
obj1.parent.childs.push(obj1);

// Everything is alright.
console.log(obj0.childs);
//  Nothing changed here:
console.log(obj1.childs);  

Ответ №2:

Согласно комментариям, проблема в том, что Object.assign это неглубокая копия, и в конечном итоге childs свойство обоих объектов является одним и тем же массивом, так что, когда вы push обращаетесь к одному childs массиву, другой также обновляется (оба childs массива являются одним и тем же).

Возможно, вы хотели бы использовать класс для определения ваших объектов:

 // Object that will be parent of obj1
class MyObj {
  constructor() {
    this.childs = [];
    this.someValue = 10;
    this.parent = null;
  }
}

var obj0 = new MyObj();
var obj1 = new MyObj();
console.log("Expected result: false; Result: "   (obj0 == obj1));
obj1.someValue = 5;
console.log("Expected result: false; Result: "   (obj0.someValue == obj1.someValue));

// Now add obj0 as parent to obj1...
obj1.parent = obj0;

// ... and add obj1 as child to obj0 - I need to do this throught .parent
// (I can't directly do some change in obj1)
obj1.parent.childs.push(obj1);

console.log("obj0's children:")
console.log(obj0.childs)
console.log("obj1's children:")
console.log(obj1.childs)