>일반적인 문제 >딥 카피의 세 가지 구현 방법은 무엇입니까?

딥 카피의 세 가지 구현 방법은 무엇입니까?

coldplay.xixi
coldplay.xixi원래의
2020-12-01 12:01:4535417검색

딥 카피의 세 가지 구현 방법은 다음과 같습니다. 1. 모든 계층적 속성을 재귀적으로 복사합니다. 2. JSON 객체의 구문 분석 및 문자열화를 사용합니다. 3. JQ의 확장 방법을 차용합니다.

딥 카피의 세 가지 구현 방법은 무엇입니까?

딥 카피의 세 가지 구현 방법은 다음과 같습니다.

1, 모든 레벨 속성을 복사하는 재귀 재귀

function deepClone(obj){
    let objClone = Array.isArray(obj)?[]:{};    if(obj && typeof obj==="object"){        for(key in obj){            if(obj.hasOwnProperty(key)){                //判断ojb子元素是否为对象,如果是,递归复制
                if(obj[key]&&typeof obj[key] ==="object"){
                    objClone[key] = deepClone(obj[key]);
                }else{                    //如果不是,简单复制
                    objClone[key] = obj[key];
                }
            }
        }
    }    return objClone;
}    
let a=[1,2,3,4],
    b=deepClone(a);
a[0]=2;
console.log(a,b);

이제 b는 a의 제어를 벗어납니다. 더 이상 a의 영향을 받지 않습니다.

딥 카피는 객체의 각 레벨의 속성을 복사하는 것임을 다시 한 번 강조합니다. JQ에는 객체를 복사할 수도 있는 확장 메소드가 있습니다. 살펴보겠습니다

let a=[1,2,3,4],
    b=a.slice();
a[0]=2;
console.log(a,b);

그러면 슬라이스 메소드도 결국에는 a의 영향을 받지 않는다는 뜻인가요? 이 예에서는 모든 수준의 속성이 여전히 복사됩니다. a를

let a=[0,1,[2,3],4],
        b=a.slice();
a[0]=1;
a[2][0]=1;
console.log(a,b);

로 변경했지만 복사가 완료되지 않았습니다. 객체 b의 첫 번째 수준 속성은 실제로 영향을 받지 않지만 두 번째 수준 속성은 여전히 ​​유지됩니다. 성공적으로 복사되지 않았으며 여전히 분리되어 있습니다. a의 제어가 없으면 슬라이스가 실제 전체 복사가 아님을 의미합니다.

다음은 Zhihu Q&A

에 있는 사진의 인용문입니다.

첫 번째 레이어의 속성은 실제로 딥 카피되어 독립적인 메모리를 가지고 있지만 더 깊은 속성은 여전히 ​​동일한 주소를 공유하므로 위의 문제가 발생합니다. .

마찬가지로 concat 방법과 슬라이스도 이러한 상황이 있습니다. 여기서는 주의할 필요가 있습니다.

2. 재귀 외에도 JSON 객체의 구문 분석 및 문자열화를 빌릴 수도 있습니다

function deepClone(obj){
    let _obj = JSON.stringify(obj),
        objClone = JSON.parse(_obj);
    return objClone
}    
let a=[0,1,[2,3],4],
    b=deepClone(a);
a[0]=1;
a[2][0]=1;
console.log(a,b);

이제 b는 a의 영향을 전혀 받지 않는다는 것을 알 수 있습니다.

그런데, 전체 복사 구현 외에도 JSON.stringify 및 JSON.parse를 localStorage와 결합하여 객체 배열 저장소를 구현할 수도 있습니다. 관심이 있으시면 블로거가 작성한 이 기사를 읽어보세요.

localStorage는 배열, 객체, localStorage, sessionStorage는 배열 객체를 저장합니다

3. 위의 두 가지 메소드 외에도 JQ의 확장 메소드를 빌릴 수 있습니다.

$.extend( [deep ], target, object1 [, objectN ] )

  • deep은 deep copy인지를 나타내며, true이면 deep copy이고, true이면 deep copy입니다. false, 얕은 복사본입니다

  • target Object 유형 대상 개체인 경우 다른 개체의 구성원 속성이 이 개체에 첨부됩니다.

  • object1 objectN선택 사항입니다. 객체 유형 첫 번째와 N번째 병합된 객체입니다.

let a=[0,1,[2,3],4],
    b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b);

위 방법과 효과는 동일하지만 JQ 라이브러리에 의존해야 함을 알 수 있습니다.

말씀드렸지만 Deep Copy를 이해하는 것은 면접 질문에 대처하는 것뿐만 아니라 실제 개발에도 매우 유용합니다. 예를 들어, 한 묶음의 데이터가 백그라운드에서 반환되는데, 이 묶음의 데이터에 대해 작업을 수행해야 하지만, 다중 사용자 개발의 경우 이 데이터 묶음이 다른 기능을 가지고 있는지 알 방법이 없습니다. 직접 수정하면 숨겨진 문제가 발생할 수 있으므로 실제 상황에 맞게 복사하면 데이터를 보다 안전하게 운영할 수 있다는 뜻일 것입니다.

4.lodash의 _.cloneDeep()

다음은 제가 읽은 딥 카피 문제에 대한 해결 방법입니다.

JSON.parse

먼저 객체를 json 객체로 변환하세요. 그런 다음 이 json 객체를 구문 분석합니다.

let obj = {a:{b:22}};let copy = JSON.parse(JSON.stringify(obj));

이 방법의 장점은 코드 작성이 비교적 간단하다는 것입니다. 하지만 단점도 분명합니다. 먼저 임시적이고 큰 문자열을 만든 다음 이를 파서에 다시 넣습니다. 또 다른 단점은 이 방법이 순환 객체를 처리할 수 없다는 것입니다.

이 방법을 사용할 때 다음 루프 객체는 예외를 발생시킵니다.

let a = {};let b = {a};a.b = b;let copy = JSON.parse(JSON.stringify(a));

딥 카피의 세 가지 구현 방법은 무엇입니까?

Map, Set, RegExp, Date, ArrayBuffer 및 기타 내장 유형은 직렬화 중에 손실됩니다.

let a = {};let b = new Set();b.add(11);a.test = b;let copy = JSON.parse(JSON.stringify(a));

a의 값은 다음과 같이 출력됩니다

딥 카피의 세 가지 구현 방법은 무엇입니까?

copy的值打印如下

딥 카피의 세 가지 구현 방법은 무엇입니까?

对比发现,Set已丢失。

Structured Clone 结构化克隆算法

MessageChannel

建立两个端,一个端发送消息,另一个端接收消息。

function structuralClone(obj) {    return new Promise(resolve =>{
        const {port1, port2} = new MessageChannel();
        port2.onmessage = ev => resolve(ev.data);
        port1.postMessage(obj);
    })
}
const obj = /* ... */;
structuralClone(obj).then(res=>{
     console.log(res);
})

这种方法的优点就是能解决循环引用的问题,还支持大量的内置数据类型。缺点就是这个方法是异步的。

History API

利用history.replaceState。这个api在做单页面应用的路由时可以做无刷新的改变url。这个对象使用结构化克隆,而且是同步的。但是我们需要注意,在单页面中不要把原有的路由逻辑搞乱了。所以我们在克隆完一个对象的时候,要恢复路由的原状。

function structuralClone(obj) {
  const oldState = history.state;
  history.replaceState(obj, document.title);
  const copy = history.state;
  history.replaceState(oldState, document.title);  return copy;
}var obj = {};var b = {obj};
obj.b = bvar copy = structuralClone(obj); 
console.log(copy);

这个方法的优点是。能解决循环对象的问题,也支持许多内置类型的克隆。并且是同步的。但是缺点就是有的浏览器对调用频率有限制。比如Safari 30 秒内只允许调用 100 次

Notification API

这个api主要是用于桌面通知的。如果你使用Facebook的时候,你肯定会发现时常在浏览器的右下角有一个弹窗,对就是这家伙。我们也可以利用这个api实现js对象的深拷贝。

function structuralClone(obj) {  return new Notification('', {data: obj, silent: true}).data;
}var obj = {};var b = {obj};
obj.b = bvar copy = structuralClone(obj);
console.log(copy)

同样是优点和缺点并存,优点就是可以解决循环对象问题,也支持许多内置类型的克隆,并且是同步的。缺点就是这个需要api的使用需要向用户请求权限,但是用在这里克隆数据的时候,不经用户授权也可以使用。在http协议的情况下会提示你再https的场景下使用。

lodash的_.cloneDeep()

支持循环对象,和大量的内置类型,对很多细节都处理的比较不错。推荐使用。

支持的类型有很多

딥 카피의 세 가지 구현 방법은 무엇입니까?

我们这里再次关注一下lodash是如何解决循环应用这个问题的?

딥 카피의 세 가지 구현 방법은 무엇입니까?

从相关的代码中。我们可以发现。lodash是用一个栈记录了。所有被拷贝的引用值。如果再次碰到同样的引用值的时候,不会再去拷贝一遍。而是利用之前已经拷贝好的值。

实现一个简易点的深拷贝,以解决循环引用的问题为目标

我们仅仅实现一个简易点的深拷贝。能优雅的处理循环引用的即可。在实现深拷贝之前,我们首先温习回顾一下js中的遍历对象的属性的方法和各种方法的优缺点。

js中遍历一个对象的属性的方法

  • Object.keys() 仅仅返回自身的可枚举属性,不包括继承来的,更不包括Symbol属性
  • Object.getOwnPropertyNames() 返回自身的可枚举和不可枚举属性。但是不包括Symbol属性
  • Object.getOwnPropertySymbols() 返回自身的Symol属性
  • for...in 可以遍历对象的自身的和继承的可枚举属性,不包含Symbol属性
  • Reflect.ownkeys() 返回对象自身的所有属性,不管是否可枚举,也不管是否是Symbol。注意不包括继承的属性

实现深拷贝,解决循环引用问题

/**
 * 判断是否是基本数据类型
 * @param value 
 */function isPrimitive(value){  return (typeof value === 'string' || 
  typeof value === 'number' || 
  typeof value === 'symbol' ||  typeof value === 'boolean')
}/**
 * 判断是否是一个js对象
 * @param value 
 */function isObject(value){  return Object.prototype.toString.call(value) === "[object Object]"}/**
 * 深拷贝一个值
 * @param value 
 */function cloneDeep(value){  // 记录被拷贝的值,避免循环引用的出现
  let memo = {};  function baseClone(value){
    let res;    // 如果是基本数据类型,则直接返回
    if(isPrimitive(value)){      return value;    // 如果是引用数据类型,我们浅拷贝一个新值来代替原来的值
    }else if(Array.isArray(value)){
      res = [...value];
    }else if(isObject(value)){
      res = {...value};
    }    // 检测我们浅拷贝的这个对象的属性值有没有是引用数据类型。如果是,则递归拷贝
    Reflect.ownKeys(res).forEach(key=>{      if(typeof res[key] === "object" && res[key]!== null){        //此处我们用memo来记录已经被拷贝过的引用地址。以此来解决循环引用的问题
        if(memo[res[key]]){
          res[key] = memo[res[key]];
        }else{
          memo[res[key]] = res[key];
          res[key] = baseClone(res[key])
        }
      }
    })    return res;  
  }  return baseClone(value)
}

验证我们写的cloneDeep是否能解决循环应用的问题

var obj = {};var b = {obj};
obj.b = bvar copy = cloneDeep(obj); 
console.log(copy);

위 내용은 딥 카피의 세 가지 구현 방법은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.