source

Javascript ES6 클래스 인스턴스를 복제하는 방법

lovecheck 2023. 8. 5. 10:38
반응형

Javascript ES6 클래스 인스턴스를 복제하는 방법

ES6를 사용하여 Javascript 클래스 인스턴스를 복제하려면 어떻게 해야 합니까?

저는 jquery나 $extend 기반의 솔루션에는 관심이 없습니다.

저는 문제가 상당히 복잡하다는 것을 시사하는 개체 복제에 대한 꽤 오래된 논의를 보아왔지만 ES6에서는 매우 간단한 해결책이 제시됩니다. 사람들이 만족스럽다고 생각하는지 아래에 설명하겠습니다.

편집: 제 질문이 중복된 것으로 제안되고 있습니다. 저는 그 답변을 보았지만 ES6 이전의 js를 사용하여 매우 복잡한 답변을 포함하고 있습니다.저는 ES6를 가능하게 하는 제 질문이 훨씬 더 간단한 해결책을 가지고 있다고 제안하고 있습니다.

그것은 복잡합니다; 저는 많은 노력을 했습니다!결국, 이 단일 라이너는 맞춤형 ES6 클래스 인스턴스에 적합했습니다.

let clone = Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)

코드 속도를 많이 늦춘다고 해서 프로토타입 설정을 피합니다.

기호를 지원하지만 게터/세터에 완벽하지 않으며 열거할 수 없는 속성으로 작동하지 않습니다(Object.assign() 문서 참조).또한 기본 내부 클래스(Array, Date, RegExp, Map 등)를 복제하는 것은 슬프게도 종종 개별적인 처리가 필요한 것처럼 보입니다.

결론:엉망진창입니다.언젠가 네이티브하고 깨끗한 클론 기능이 있기를 바랍니다.

const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

Object.assign의 특성에 유의하십시오. 이는 얕은 복사를 수행하며 클래스 메서드를 복사하지 않습니다.

전체 복사본 또는 복사본에 대한 더 많은 제어를 원하는 경우 lodash 클론 기능이 있습니다.

저는 거의 모든 답을 좋아합니다.나는 이 문제가 있었고 그것을 해결하기 위해 나는 그것을 정의함으로써 수동으로 할 것입니다.clone()방법과 그 안에서, 나는 처음부터 전체 물체를 만들 것입니다.생성된 개체는 자연스럽게 복제된 개체와 동일한 유형이 되기 때문에 이것이 타당합니다.

유형 스크립트를 사용한 예제:

export default class ClassName {
    private name: string;
    private anotherVariable: string;
   
    constructor(name: string, anotherVariable: string) {
        this.name = name;
        this.anotherVariable = anotherVariable;
    }

    public clone(): ClassName {
        return new ClassName(this.name, this.anotherVariable);
    }
}

나는 이 솔루션이 더 '객체 지향적'으로 보이기 때문에 좋습니다.

TLDR;

// Use this approach
//Method 1 - clone will inherit the prototype methods of the original.
    let cloneWithPrototype = Object.assign(Object.create(Object.getPrototypeOf(original)), original); 

Javascript에서 프로토타입을 확장하는 것은 권장되지 않습니다. 코드/구성 요소를 테스트할 때 문제가 발생합니다.유닛 테스트 프레임워크는 프로토타입 확장을 자동으로 가정하지 않습니다.그래서 그것은 좋은 관행이 아닙니다.프로토타입 확장에 대한 더 많은 설명이 있습니다. 네이티브 개체를 확장하는 것이 좋지 않은 이유는 무엇입니까?

JavaScript에서 개체를 복제하는 방법은 간단하거나 간단한 방법이 아닙니다.다음은 "Shallow Copy"를 사용하는 첫 번째 인스턴스입니다.

1 -> 얕은 클론:

class Employee {
    constructor(first, last, street) {
        this.firstName = first;
        this.lastName = last;
        this.address = { street: street };
    }

    logFullName() {
        console.log(this.firstName + ' ' + this.lastName);
    }
}

let original = new Employee('Cassio', 'Seffrin', 'Street A, 23');

//Method 1 - clone will inherit the prototype methods of the original.
let cloneWithPrototype = Object.assign(Object.create(Object.getPrototypeOf(original)), original); 

//Method 2 - object.assing() will not clone the Prototype.
let cloneWithoutPrototype =  Object.assign({},original); 

//Method 3 - the same of object assign but shorter syntax using "spread operator"
let clone3 = { ...original }; 

//tests
cloneWithoutPrototype.firstName = 'John';
cloneWithoutPrototype.address.street = 'Street B, 99'; //will not be cloned

결과:

original.logFullName();

결과: 카시오 세프린

cloneWithPrototype.logFullName();

결과: 카시오 세프린

original.address.street;

결과: 'Street B, 99' // 원래 하위 개체가 변경되었음을 알립니다.

주의: 인스턴스에 고유 속성으로 폐쇄가 있는 경우 이 메서드는 이를 래핑하지 않습니다. (폐쇄에 대한 자세한 내용을 읽어 보십시오) 그리고 하위 개체 "주소"가 복제되지 않습니다.

prototype.logFullName() 없이 clone

작동하지 않습니다.복제본은 원본의 프로토타입 메서드를 상속하지 않습니다.

clonePrototype.logFullName()을 사용하여 복제합니다.

클론이 프로토타입도 복사하기 때문에 작동합니다.

Object.assign을 사용하여 어레이를 복제하는 방법

let cloneArr = array.map((a) => Object.assign({}, a));

세금에서 ECMA스크립트 스프레드를 사용하여 어레이 복제:

let cloneArrSpread = array.map((a) => ({ ...a }));

2 -> 딥 클론:

완전히 새로운 객체 참조를 보관하려면 JSON.stringify()를 사용하여 원래 객체를 문자열로 구문 분석한 후 JSON.parse()로 다시 구문 분석할 수 있습니다.

let deepClone = JSON.parse(JSON.stringify(original));

딥 클론을 사용하면 주소에 대한 참조가 유지됩니다.그러나 deepClone 프로토타입은 손실되므로 deepClone.logFullName()이 작동하지 않습니다.

3 -> 타사 라이브러리:

다른 옵션은 loadash 또는 언더스코어와 같은 타사 라이브러리를 사용하는 것입니다.새 개체를 만들고 각 값을 원래 개체에서 새 개체로 복사하여 참조를 메모리에 유지합니다.

밑줄: Underscore = _(original)을 복제합니다.클론 »;

Loadash 클론: var cloneLodash = _.cloneDeep(원래);

Lodash 또는 밑줄의 단점은 프로젝트에 추가 라이브러리를 포함해야 한다는 것입니다.그러나 이 옵션은 좋은 옵션이며 높은 성능 결과를 산출하기도 합니다.

원래 개체와 동일한 프로토타입 및 동일한 속성을 사용하여 개체의 복사본을 만듭니다.

function clone(obj) {
  return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))
}

셀 수 없는 속성, 게터, 세터 등으로 작동합니다.많은 기본 제공 Javascript 유형(예: Array, Map, Proxy)이 있는 내부 슬롯을 복제할 수 없습니다.

서로 확장된 클래스가 여러 개인 경우 각 인스턴스의 클론에 대한 최상의 솔루션은 클래스 정의에서 해당 개체의 새 인스턴스를 생성하는 함수 하나를 정의하는 것입니다.

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  clone() {
    return new Point(this.x, this.y);
  }
}
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);
    this.color = color;
  }
  clone() {
    return new ColorPoint(
      this.x, this.y, this.color.clone()); // (A)
  }
}

이제 클론을 사용할 수 있습니다(다음과 같은 객체의 0 함수).

let p = new ColorPoint(10,10,'red');
let pclone=p.clone();

사용해 보십시오.

function copy(obj) {
   //Edge case
   if(obj == null || typeof obj !== "object") { return obj; }

   var result = {};
   var keys_ = Object.getOwnPropertyNames(obj);

   for(var i = 0; i < keys_.length; i++) {
       var key = keys_[i], value = copy(obj[key]);
       result[key] = value;
   }

   Object.setPrototypeOf(result, obj.__proto__);

   return result;
}

//test
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
};

var myPoint = new Point(0, 1);

var copiedPoint = copy(myPoint);

console.log(
   copiedPoint,
   copiedPoint instanceof Point,
   copiedPoint === myPoint
);
Since it uses Object.getOwnPropertyNames, it will also add non-enumerable properties.

또 다른 라이너:

대부분의 시간은...(Date, RegExp, Map, String, Number, Array에 사용 가능), btw, cloning string, number는 약간 재미있습니다.

let clone = new obj.constructor(...[obj].flat())

복사 생성자가 없는 클래스의 경우:

let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)

class A {
  constructor() {
    this.x = 1;
  }

  y() {
    return 1;
  }
}

const a = new A();

const output =  Object.getOwnPropertyNames(Object.getPrototypeOf(a))
  .concat(Object.getOwnPropertyNames(a))
  .reduce((accumulator, currentValue, currentIndex, array) => {
    accumulator[currentValue] = a[currentValue];
    return accumulator;
  }, {});
  
console.log(output);

enter image description here

이렇게 하는 것이 충분하지 않나요?

Object.assign(new ClassName(), obj)

저는 로다쉬를 썼어요.

import _ from 'lodash'
class Car {
    clone() { return _.cloneDeep(this); }
}

지금까지 받은 모든 답변에 문제가 있기 때문에 OP에 대한 보다 완전한 답변입니다(일부 경우 다른 사례와 시나리오에서 작동하지 않는 것이 아니라 요청대로 ES6만 사용하는 가장 간단한 범용 답변이 아닙니다).후세를 위하여.

object.assign()은 응답자가 언급한 대로 얕은 복사만 수행합니다.자바스크립트 가비지 컬렉션은 원래 개체에서 모든 참조가 제거될 때만 작동하기 때문에 실제로 큰 문제입니다.즉, 거의 변경되지 않는 단순한 풀에 대해서도 오래된 개체를 참조하여 수행되는 클로닝은 잠재적으로 중요한 메모리 누수를 의미합니다.

개체에 데이터 하위 트리가 하나라도 있는 경우 이전 인스턴스를 참조할 수 있는 새 인스턴스를 생성하는 경우 클래스가 "clone()" 메서드로 확장되면 Object.assign()과 동일한 가비지 수집 문제가 발생합니다.이것은 혼자서 관리하기 어려울 것입니다.

스프레드 연산자("...")를 사용하는 것도 위와 같은 참조 및 고유성 문제인 어레이/개체의 얕은 복사본입니다.또한 답변에 대한 응답에서 언급했듯이, 이것은 어쨌든 프로토타입과 클래스를 잃게 됩니다.

프로토타입이 확실히 더 느린 방법이지만 V8은 그 접근 방식으로 성능 문제를 해결했다고 생각하기 때문에 2022년에는 더 이상 문제가 되지 않을 것 같습니다.

2022년에 대한 제안된 답변: 모든 클래스 개체 데이터를 가져오기 위해 딥 카피 스크립트를 적절하게 작성합니다.클래스 개체를 복제하려면 임시 컨테이너를 만들고 클래스 개체를 임시 컨테이너에 심층 복사합니다.모든 메서드가 포함된 상위 클래스(슈퍼 클래스)와 개체 데이터 및 인스턴스에 사용할 하위 클래스를 작성합니다.그런 다음 확장된 하위 클래스에서 상위 클래스의 메서드를 호출할 때 하위 클래스의 'this'를 인수로 전달하고 상위 클래스의 메서드에서 해당 인수를 가져옵니다(예: 'that'라는 단어를 사용합니다).마지막으로 개체 데이터를 임시 개체로 복제할 때 복제할 모든 개체의 새 인스턴스를 생성하고 이전 인스턴스에 대한 참조를 새 인스턴스로 교체하여 메모리에 남아 있지 않도록 합니다.예를 들어, 저는 콘웨이의 삶의 게임의 진부한 버전을 만들고 있습니다."모든 셀"이라는 배열을 각 요청에 업데이트할 때 AnimationFrame(renderFunction)에 모든 셀을 딥 복사하고 부모의 업데이트(that) 메서드를 호출하는 각 셀의 업데이트(this) 메서드를 실행한 다음 새 셀(temp[0]x, temp[0]y,모든 업데이트가 완료된 후 이전 "모든 셀" 컨테이너를 교체할 수 있는 배열로 모든 파일을 패키지화합니다.라이프 게임 예제에서 임시 컨테이너에서 업데이트를 수행하지 않으면 이전 업데이트가 동일한 시간 단계 내에 후자 업데이트의 출력에 영향을 미치므로 바람직하지 않을 수 있습니다.

완료! 로다쉬도, 타자기도, jQuery도, 요청하신 대로 ES6만 가능합니다.이상하게 보이지만 일반 재귀 복사() 스크립트를 작성하는 경우 위에서 설명한 단계를 따르고 아래 예제 코드를 참조용으로 사용하려면 이를 사용하여 클론() 함수를 만드는 함수를 쉽게 작성할 수 있습니다.

function recursiveCopy(arr_obj){
    if(typeof arr_obj === "object") {
        if ( Array.isArray(arr_obj) ) {
            let result = []
            // if the current element is an array
            arr_obj.forEach( v => { result.push(recursiveCopy(v)) } )
            return result 
        }
        else {
            // if it's an object by not an array then it’s an object proper { like: “so” }
            let result = {}
            for (let item in arr_obj) {
                result[item] = recursiveCopy(arr_obj[item]) // in case the element is another object/array
            }
            return result
        }
    }
    // above conditions are skipped if current element is not an object or array, so it just returns itself
    else if ( (typeof arr_obj === "number") || (typeof arr_obj === "string") || (typeof arr_obj === "boolean") ) return arr_obj
    else if(typeof arr_obj === "function") return console.log("function, skipping the methods, doing these separately")
    else return new Error( arr_obj ) // catch-all, likely null arg or something
}

// PARENT FOR METHODS
class CellMethods{
    constructor(){
        this.numNeighboursSelected = 0
    }

    // method to change fill or stroke color
    changeColor(rgba_str, str_fill_or_stroke, that) {
        // DEV: use switch so we can adjust more than just background and border, maybe text too
        switch(str_fill_or_stroke) {
        case 'stroke':
            return that.border = rgba_str
        default:      // fill is the default
            return that.color = rgba_str
        }
    }

    // method for the cell to draw itself
    drawCell(that){
        // save existing values
        let tmp_fill = c.fillStyle
        let tmp_stroke = c.strokeStyle
        let tmp_borderwidth = c.lineWidth
        let tmp_font = c.font
        
        // fill and stroke cells
        c.fillStyle = (that.isSelected) ? highlightedcellcolor : that.color
        c.strokeStyle = that.border
        c.lineWidth = border_width
        c.fillRect(that.x, that.y, that.size.width, that.size.height)
        c.strokeRect(that.x, that.y, that.size.width+border_width, that.size.height+border_width)
        
        // text id labels
        c.fillStyle = that.textColor
        c.font = `${that.textSize}px Arial`
        c.fillText(that.id, that.x+(cellgaps*3), that.y+(that.size.height-(cellgaps*3)))
        c.font = tmp_font

        // restore canvas stroke and fill
        c.fillStyle = tmp_fill
        c.strokeStyle = tmp_stroke
        c.lineWidth = tmp_borderwidth    
    }
    checkRules(that){
        console.log("checking that 'that' works: " + that)
        if ((that.leftNeighbour !== undefined) && (that.rightNeighbour !== undefined) && (that.topNeighbour !== undefined) && (that.bottomNeighbour !== undefined) && (that.bottomleft !== undefined) && (that.bottomright !== undefined) && (that.topleft !== undefined) && (that.topright !== undefined)) {
            that.numNeighboursSelected = 0
            if (that.leftNeighbour.isSelected) that.numNeighboursSelected++
            if (that.rightNeighbour.isSelected) that.numNeighboursSelected++
            if (that.topNeighbour.isSelected) that.numNeighboursSelected++
            if (that.bottomNeighbour.isSelected) that.numNeighboursSelected++
            // // if my neighbours are selected
            if (that.numNeighboursSelected > 5) that.isSelected = false
        }
    }
}

// write a class to define structure of each cell
class Cell extends CellMethods{
    constructor(id, x, y, selected){
        super()
        this.id = id
        this.x = x
        this.y = y
        this.size = cellsize
        this.color = defaultcolor
        this.border = 'rgba(0,0,0,1)'
        this.textColor = 'rgba(0,0,0,1)'
        this.textSize = cellsize.height/5     // dynamically adjust text size based on the cell's height, since window is usually wider than it is tall
        this.isSelected = (selected) ? selected : false
    }
    changeColor(rgba_str, str_fill_or_stroke){ super.changeColor(rgba_str, str_fill_or_stroke, this)} // THIS becomes THAT
    checkRules(){ super.checkRules(this) } // THIS becomes THAT
    drawCell(){ super.drawCell(this) } // THIS becomes THAT
}

let [cellsincol, cellsinrow, cellsize, defaultcolor] = [15, 10, 25, 'rgb(0,0,0)'] // for building a grid
// Bundle all the cell objects into an array to pass into a render function whenever we want to draw all the objects which have been created
function buildCellTable(){
    let result = []  // initial array to push rows into
    for (let col = 0; col < cellsincol; col++) {  // cellsincol aka the row index within the column
    let row = []
    for (let cellrow = 0; cellrow < cellsinrow; cellrow++) {  // cellsinrow aka the column index
        let newid = `col${cellrow}_row${col}` // create string for unique id's based on array indices
        row.push( new Cell(newid, cellrow*(cellsize.width),col*(cellsize.height) ))
    }
    result.push(row)
    }
    return result
}

// poplate array of all cells, final output is a 2d array
let allcells = buildCellTable()

// create hash table of allcells indexes by cell id's
let cellidhashtable = {}
allcells.forEach( (v,rowindex)=>{
    v.forEach( (val, colindex)=>{
    cellidhashtable[val.id] = [rowindex, colindex]  // generate hashtable 
    val.allcellsposition = [rowindex, colindex]     // add cell indexes in allcells to each cell for future reference if already selected    
    } )
})

// DEMONSTRATION
let originalTable = {'arr': [1,2,3,4,5], 'nested': [['a','b','c'], ['d','e','f']], 'obj': {'nest_obj' : 'object value'}}
let newTable = recursiveCopy(originalTable) // works to copy
let testingDeepCopy = recursiveCopy(newTable)
let testingShallowCopy = {...newTable}  // spread operator does a unique instance, but references nested elements
newTable.arr.pop() // removes an element from a nested array after popping
console.log(testingDeepCopy)   // still has the popped value
console.log(testingShallowCopy)  // popped value is remove even though it was copies before popping

// DEMONSTRATION ANSWER WORKS
let newCell = new Cell("cell_id", 10, 20)
newCell.checkRules()

예를 들어 Obj:라는 이름의 개체를 복제하려는 경우 스프레드 연산자를 사용할 수 있습니다.

let clone = { ...obj};

복제된 개체를 변경하거나 추가하려는 경우:

let clone = { ...obj, change: "something" };

언급URL : https://stackoverflow.com/questions/41474986/how-to-clone-a-javascript-es6-class-instance

반응형