>  기사  >  웹 프론트엔드  >  JavaScript 주제 5: 깊은 복사와 얕은 복사

JavaScript 주제 5: 깊은 복사와 얕은 복사

coldplay.xixi
coldplay.xixi앞으로
2021-03-08 09:36:141929검색

JavaScript 주제 5: 깊은 복사와 얕은 복사

복사 과정을 이해하고 불필요한 실수를 피하세요. Js의 깊고 얕은 복사 시리즈를 함께 열심히 만들어 보세요~

목차

  • 1 복사 예
  • 2.
  • 3 , 딥카피 방식?
  • 4. 직접 Deep Copy와 Shallow Copy를 구현하세요

추천 무료 학습: javascript 비디오 튜토리얼

1.데이터를 조작할 때 이런 문제가 발생할 수 있습니다. 상황:

데이터 세트가 자주 변경되지만 원본 데이터가 사용될 수 있습니다.

    동일한 데이터 세트 두 개가 필요하지만 하나를 변경하고 싶지 않고 다른 하나가 변경됩니다
  1. 필요합니다 데이터 연산 전후 비교
  2. 비슷한 요구 상황에 직면했을 때 가장 먼저 생각하는 것은 복사하는 것인데, 복사도 지식이 많은지 모르겠습니다~
당신은 다음의 간단한 예에 익숙해지셨나요?

1.1 기본 유형 복사 예

var str = 'How are you';var newStr = str;newStr = 10console.log(str); // How are youconsole.log(newStr); // 10
모두가 상상할 수 있듯이 문자열은 기본 유형이며 해당 값은 복사할 때 실제로 새로운 변수를 위한 새로운 공간을 엽니다. strnewStr은 레이아웃은 동일하지만 연결이 없는 두 개의 동일한 방과 같습니다.
1.2 참조 유형 복사 예

var data = [1, 2, 3, 4, 5];var newData = data;newData[0] = 100;console.log(data[0]); // 100console.log(newData[0]); // 100
strnewStr就好比两个一模一样的房间,布局一致却毫无关联。
1.2 引用类型拷贝示例
var arr = [1, 2, 3, 4];var newArr = arr;console.log(arr, newArr); // [1,2,3,4] [1,2,3,4]newArr[0] = 100;console.log(arr, newArr) // [100,2,3,4] [100,2,3,4]

类似的代码段,但这次我们使用数组这个引用类型举例,你会发现修改赋值后的数据,原始数据也跟着改变了,这显然不满足我们的需要。本篇文章就来聊一聊引用数据拷贝的学问

如果大家对Js的数据类型存在着疑问,不妨看看《JavaScript中的基本数据类型》

JavaScript 주제 5: 깊은 복사와 얕은 복사

二、浅拷贝

拷贝的划分都是针对引用类型来讨论的,浅拷贝——顾名思义,浅拷贝就是“浅层拷贝”,实际上只做了表面功夫:

var arr = [1,2,3,4];var res = arr.slice();// 或者res = arr.concat()res[0] = 100;console.log(arr); // [1,2,3,4]

不发生事情(操作)还好,一旦对新数组进行了操作,两个变量中所保存的数据都会发生改变。

发生这类情况的原因也是因为引用类型的基本特性:

  • 存储在变量处的值是一个指针(point),指向存储对象的内存地址。赋值给新变量相当于配了一把新钥匙,房间并没有换。

数组中的slice和concat都会返回一个新数组,我们一起来试一下:

var arr = [ { age: 23 }, [1,2,3,4] ];var newArr = arr.concat();arr[0].age = 18;arr[1][0] = 100;console.log(arr) // [ {age: 18}, [100,2,3,4] ]console.log(newArr) // [ {age: 18}, [100,2,3,4] ]

这个问题这么快就解决了?虽然对这一层数据进行了这样的的处理后,确实解决了问题,但!

var arr = ['str', 1, true, [1, 2], {age: 23}]var newArr = JSON.parse( JSON.stringify(arr) );newArr[3][0] = 100;console.log(arr); // ['str', 1, true, [1, 2], {age: 23}]console.log(newArr); // ['str', 1, true, [100, 2], {age: 23}]

果然事情没有那么简单,这也是因为数据类型的不同。

S 不允许我们直接操作内存中的地址,也就是说不能操作对象的内存空间,所以,我们对对象的操作都只是在操作它的引用而已。

既然浅拷贝达不到我们的要求,本着效率的原则,我们找找有没有帮助我们实现深拷贝的方法。

JavaScript 주제 5: 깊은 복사와 얕은 복사

三、深拷贝的方法?

数据的方法失败了,还有没有其他办法?我们需要实现真正意义上的拷贝出独立的数据。

3.1 JSON

这里我们利用JSON的两个方法,JSON.stringify()JSON.parse()来实现最简洁的深拷贝

var arr = [1,2,3];var newArr = arr.toString().split(',').map(item => Number(item))newArr[0] = 100;console.log(arr); // [1,2,3]console.log(newArr); // [100,2,3]

这个方法应该是实现深拷贝最简洁的方法,但是,它仍然存在问题,我们先来看看刚才都做了些什么:

  1. 定义一个包含都过类型的数组arr
  2. JSON.stringify(arr), 将一个 JavaScript 对象或值转换为 JSON 字符串
  3. JSON.parse(xxx), 方法用来解析JSON字符串,构造由字符串描述的值或对象

理解:

我们可以理解为,将原始数据转换为新字符串,再通过新字符串还原为一个新对象,这中改变数据类型的方式,间接的绕过了拷贝对象引用的过程,也就谈不上影响原始数据。

限制:

这种方式成立的根本就是保证数据在“中转”时的完整性,而JSON.stringify()将值转换为相应的JSON格式비슷한 코드 조각이지만 이번에는 참조 유형 배열을 예로 사용합니다. 할당된 데이터가 수정되면 원본 데이터도 변경되므로 분명히 일치하지 않습니다. 우리의 필요. 이번 글에서는

데이터 복사 인용 지식🎜에 대해 이야기하겠습니다. 🎜🎜🎜Js의 데이터 유형에 대해 궁금한 점이 있으면 "JavaScript의 기본 데이터 유형"을 살펴보는 것이 좋습니다.🎜🎜🎜여기에 그림 설명 삽입🎜🎜🎜 2. 얕은 사본 🎜🎜🎜 사본의 분할은 참조 유형에 따라 논의됩니다. 얕은 사본 - 이름대로 얕은 복사를 의미합니다. 실제로는 피상적인 작업만 수행합니다. 🎜
var obj = {a: 1, b: { c: 2 } }var newObj = Object.assign({}, obj)newObj.a = 100;newObj.b.c = 200;console.log(obj); // {a: 1, b: { c: 200 } }console.log(newObj) // {a: 100, b: { c: 200 } }
🎜아무 일도 일어나지 않습니다(작업). 다행히 새 배열이 작동되면 두 변수에 저장된 데이터가 변경됩니다. 🎜🎜이런 상황이 발생하는 이유도 참조 유형의 기본 특성 때문입니다. 🎜🎜🎜변수에 저장된 값은 메모리 주소를 가리키는 포인터(포인트)입니다. 저장된 개체. 새 변수에 값을 할당하는 것은 새 키를 할당하는 것과 동일하며 방은 변경되지 않았습니다. 🎜🎜🎜 배열의 슬라이스와 연결 모두 새 배열을 반환합니다. 함께 시도해 보겠습니다. 🎜
var shallowCopy = function(obj) {
    if (typeof obj !== 'object') return;

    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;}
🎜이 문제가 그렇게 빨리 해결되나요? 이 데이터 레이어는 이런 방식으로 처리되지만 문제는 실제로 해결되지만! 🎜
var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;}
🎜물론 일이 그렇게 간단하지는 않습니다. 이는 데이터 유형이 다르기 때문이기도 합니다. 🎜🎜S는 메모리의 주소를 직접 조작하는 것을 허용하지 않습니다. 즉, 객체의 메모리 공간을 조작할 수 없습니다. 따라서 객체에 대한 조작은 해당 참조에서만 작동합니다. 🎜🎜shallow copy는 우리의 요구 사항을 충족할 수 없기 때문에 🎜efficiency🎜 원칙에 따라 deep copy를 구현하는 데 도움이 되는 방법을 찾고 있습니다. 🎜🎜여기에 이미지 설명 삽입🎜🎜🎜세 가지, 깊은 방법 복사하려고? 🎜🎜🎜데이터 방식이 실패했습니다. 다른 방법이 있나요? 우리는 독립적인 데이터의 실제 복사본을 확보해야 합니다. 🎜🎜3.1 JSON🎜🎜여기에서는 JSON의 두 가지 방법인 JSON.stringify(), JSON.parse()를 사용하여 가장 간결한 전체 복사를 달성합니다🎜
const obj = [1, { a: 1, b: { name: '余光'} } ];const resObj = deepCopy(obj);
🎜 메소드는 딥 카피를 구현하는 가장 간단한 방법이어야 하지만 🎜여전히 문제가 있습니다🎜. 먼저 방금 수행한 작업을 살펴보겠습니다. 🎜🎜🎜모든 유형의 코드를 포함하는 arr 배열을 정의합니다.🎜🎜 JSON.stringify(arr)는 JavaScript 개체 또는 값을 <code>JSON 문자열🎜🎜JSON.parse(xxx)로 변환합니다. 메서드는 JSON 문자열을 구문 분석하는 데 사용되며 문자열에서 구성됩니다. 설명된 값 또는 object🎜🎜🎜🎜이해: 🎜🎜🎜원본 데이터를 새 문자열로 변환한 다음 새 문자열을 전달하면 복원된다고 이해하면 됩니다. 데이터 유형을 변경하는 방법은 개체 참조를 복사하는 과정을 간접적으로 우회하며 원본 데이터에는 영향을 주지 않습니다. 🎜🎜🎜제한 사항: 🎜🎜🎜이 방법의 기본 목적은 "전송" 중에 데이터의 무결성을 보장하는 것이며 JSON.stringify()는 값을 해당 JSON 형식으로 변환합니다. 에도 결함이 있습니다: 🎜
  • undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
  • 函数、undefined 被单独转换时,会返回 undefined,
    • 如JSON.stringify(function(){})
    • JSON.stringify(undefined)
  • 对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。
  • NaN 和 Infinity 格式的数值及 null 都会被当做 null。
  • 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性。

所以当我们拷贝函数、undefined等stringify转换有问题的数据时,就会出错,我们在实际开发中也要结合实际情况使用。

举一反三:

既然是通过改变数据类型来绕过拷贝引用这一过程,那么单纯的数组深拷贝是不是可以通过现有的几个API来实现呢?

var arr = [1,2,3];var newArr = arr.toString().split(',').map(item => Number(item))newArr[0] = 100;console.log(arr); // [1,2,3]console.log(newArr); // [100,2,3]

注意,此时仅能对包含纯数字的数组进行深拷贝,因为:

  1. toString无法正确的处理对象和函数
  2. Number无法处理 false、undefined等数据类型

但我愿称它为纯数字数组深拷贝

JavaScript 주제 5: 깊은 복사와 얕은 복사

3.2 Object.assign()

有的人会认为Object.assign(),可以做到深拷贝,我们来看一下

var obj = {a: 1, b: { c: 2 } }var newObj = Object.assign({}, obj)newObj.a = 100;newObj.b.c = 200;console.log(obj); // {a: 1, b: { c: 200 } }console.log(newObj) // {a: 100, b: { c: 200 } }

神奇,第一层属性没有改变,但第二层却同步改变了,这是为什么呢?

因为 Object.assign()拷贝的是(可枚举)属性值。

假如源值是一个对象的引用,它仅仅会复制其引用值。MDN传送门

四、自己实现深浅拷贝

既然现有的方法无法实现深拷贝,不妨我们自己来实现一个吧~

4.1 浅拷贝

我们只需要将所有属性即其嵌套属性原封不动的复制给新变量一份即可,抛开现有的方法,我们应该怎么做呢?

var shallowCopy = function(obj) {
    if (typeof obj !== 'object') return;

    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;}

我们只需要将所有属性的引用拷贝一份即可~

4.2 深拷贝

相信大家在实现深拷贝的时候都会想到递归,同样是判断属性值,但如果当前类型为object则证明需要继续递归,直到最后

var deepCopy = function(obj) {
    if (typeof obj !== 'object') return;
    var newObj = obj instanceof Array ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;}

我们用白话来解释一下deepCopy都做了什么

const obj = [1, { a: 1, b: { name: '余光'} } ];const resObj = deepCopy(obj);
  • 读取 obj,创建 第一个newObj
    • 判断类型为 []
    • key为 0 (for in 以任意顺序遍历,我们假定按正常循序遍历)
    • 判断不是引用类型,直接复制
    • key为 1
    • 判断是引用类型
    • 进入递归,重新走了一遍刚才的流程,只不过读取的是obj[1]

另外请注意递归的方式虽然可以深拷贝,但是在性能上肯定不如浅拷贝,大家还是需要结合实际情况来选择。

写在最后

JavaScript 주제 5: 깊은 복사와 얕은 복사

前端专项进阶系列的第五篇文章,希望它能对大家有所帮助,如果大家有什么建议,可以在评论区留言,能帮到自己和大家就是我最大的动力!

相关免费学习推荐:javascript(视频)

위 내용은 JavaScript 주제 5: 깊은 복사와 얕은 복사의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제