ホームページ > 記事 > ウェブフロントエンド > JSの深いコピーと浅いコピーの使い方を詳しく解説
今回は、JS の深いコピーと浅いコピーの使用について詳しく説明します。JS の深いコピーと浅いコピーを使用する際の 注意事項 について、実際のケースを見てみましょう。
はじめに
深いコピーと浅いコピーについて言えば、まず JavaScript の データ型 について言及する必要があります。JavaScript の基本 - データ型については、ここでは詳しく説明しません。
知っておくべきことは 1 つあります。JavaScript のデータ型は、基本データ型と参照データ型に分かれています。
基本データ型のコピーの場合、ダーク コピーとシャロー コピーと呼ばれるものは、参照データ型のものです。
浅いコピー
浅いコピーとは、参照のみがコピーされ、実際の値はコピーされないことを意味します。
const originArray = [1,2,3,4,5]; const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; const cloneArray = originArray; const cloneObj = originObj; console.log(cloneArray); // [1,2,3,4,5] console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}} cloneArray.push(6); cloneObj.a = {aa:'aa'}; console.log(cloneArray); // [1,2,3,4,5,6] console.log(originArray); // [1,2,3,4,5,6] console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}} console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
上記のコードは、 = 代入演算子を使用して浅いコピーを実装する最も簡単な方法です。cloneArray
と cloneObj
が変更されると、originArray と originObj
も変更されました。 cloneArray
和 cloneObj
改变,originArray
和 originObj
也随着发生了变化。
深拷贝
深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。
只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
目前实现深拷贝的方法不多,主要是两种:
利用 JSON 对象中的 parse 和 stringify
利用递归来实现每一层都重新创建对象并赋值
JSON.stringify/parse的方法
先看看这两个方法吧:
The JSON.stringify() method converts a JavaScript value to a JSON string.
JSON.stringify
是将一个 JavaScript 值转成一个 JSON 字符串。
The JSON.parse() method parses a JSON string, constructing the JavaScript value or object described by the string.
JSON.parse
是将一个 JSON 字符串转成一个 JavaScript 值或对象。
很好理解吧,就是 JavaScript 值和 JSON 字符串的相互转换。
它能实现深拷贝呢?我们来试试。
const originArray = [1,2,3,4,5]; const cloneArray = JSON.parse(JSON.stringify(originArray)); console.log(cloneArray === originArray); // false const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; const cloneObj = JSON.parse(JSON.stringify(originObj)); console.log(cloneObj === originObj); // false cloneObj.a = 'aa'; cloneObj.c = [1,1,1]; cloneObj.d.dd = 'doubled'; console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}}; console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
确实是深拷贝,也很方便。但是,这个方法只能适用于一些简单的情况。比如下面这样的一个对象就不适用:
const originObj = { name:'axuebin', sayHello:function(){ console.log('Hello World'); } } console.log(originObj); // {name: "axuebin", sayHello: ƒ} const cloneObj = JSON.parse(JSON.stringify(originObj)); console.log(cloneObj); // {name: "axuebin"}
发现在 cloneObj
中,有属性丢失了。。。那是为什么呢?
在 MDN 上找到了原因:
If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).
undefined
、function
、symbol
ディープコピー
ディープコピーは、参照のレイヤーのみをコピーする浅いコピーとは異なり、値もコピーされます。
ディープコピーが作成される限り、それらは決して相互に作用することはなく、誰も他のものに影響を与えることはありません。 現在、ディープコピーを実装する方法はあまり多くありませんが、主に次の 2 つの方法があります:し、値を割り当てます
まずこれら 2 つのメソッドを見てみましょう:
🎜JSON.stringify() メソッドは JavaScript 値を変換します
JSON.stringify
は、JavaScript 値を JSON 文字列に変換します。 🎜🎜 JSON.parse() メソッドは JSON 文字列を解析し、その文字列で記述された JavaScript 値またはオブジェクトを構築します。🎜🎜
JSON.parse
は、JSON 文字列を JavaScript 値またはオブジェクトに変換します。 🎜🎜分かりやすいですが、JavaScriptの値とJSON文字列の変換です。 🎜🎜ディープコピーを実現できますか?試してみよう。 🎜function deepClone(source){ const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象 for(let keys in source){ // 遍历目标 if(source.hasOwnProperty(keys)){ if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下 targetObj[keys] = source[keys].constructor === Array ? [] : {}; targetObj[keys] = deepClone(source[keys]); }else{ // 如果不是,就直接赋值 targetObj[keys] = source[keys]; } } } return targetObj; }🎜確かにディープコピーでとても便利です。ただし、この方法はいくつかの単純な状況にのみ適用できます。たとえば、次のオブジェクトは適用できません: 🎜
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; const cloneObj = deepClone(originObj); console.log(cloneObj === originObj); // false cloneObj.a = 'aa'; cloneObj.c = [1,1,1]; cloneObj.d.dd = 'doubled'; console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}}; console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};🎜
cloneObj
で、いくつかの属性が欠落していることがわかりました。 。 。なぜ? 🎜🎜 MDN で理由が見つかりました: 🎜🎜 未定義、関数、またはシンボルが変換中に見つかった場合、それは省略される (オブジェクト内で見つかった場合) か、 (配列内で見つかった場合は) null に打ち切られます。JSON.stringify(function(){}) や JSON.stringify(unknown) のような「純粋な」値を渡す場合、JSON.stringify は単に未定義を返すこともできます。 🎜unknown
、function
、symbol
は、変換プロセス中に無視されます。 。 。 🎜🎜理解してください。つまり、オブジェクトに関数 (非常に一般的) が含まれている場合、このメソッドを使用してディープ コピーを実行することはできません。 🎜🎜🎜再帰メソッド🎜🎜🎜 再帰の考え方は非常にシンプルで、データの各層に対してオブジェクト -> オブジェクトの割り当てを作成することです。 コードはシンプルで粗雑です: 🎜const originObj = { name:'axuebin', sayHello:function(){ console.log('Hello World'); } } console.log(originObj); // {name: "axuebin", sayHello: ƒ} const cloneObj = deepClone(originObj); console.log(cloneObj); // {name: "axuebin", sayHello: ƒ}🎜試してみましょう: 🎜const originArray = [1,2,3,4,5]; const cloneArray = originArray.concat(); console.log(cloneArray === originArray); // false cloneArray.push(6); // [1,2,3,4,5,6] console.log(originArray); [1,2,3,4,5];。 🎜わかりました。次に、関数を備えたものを試してください: 🎜const originArray = [1,[1,2,3],{a:1}]; const cloneArray = originArray.concat(); console.log(cloneArray === originArray); // false cloneArray[1].push(4); cloneArray[2].a = 2; console.log(originArray); // [1,[1,2,3,4],{a:2}]🎜 も OK です。終わり。 🎜🎜これで終わりだと思いますか? ? もちろん違います。 🎜🎜🎜JavaScript の Copy メソッド🎜🎜🎜 JavaScript では、配列には、元の配列をコピーできる concat とスライスの 2 つのメソッドがあることがわかっています。これらの 2 つのメソッドは、元の配列を変更せず、新しく変更された配列を返します。 🎜同时,ES6 中 引入了
Object.assgn
方法和 ... 展开运算符也能实现对对象的拷贝。那它们是浅拷贝还是深拷贝呢?
concat
The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
该方法可以连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。
看着这意思,很像是深拷贝啊,我们来试试:
const originArray = [1,2,3,4,5]; const cloneArray = originArray.concat(); console.log(cloneArray === originArray); // false cloneArray.push(6); // [1,2,3,4,5,6] console.log(originArray); [1,2,3,4,5];看上去是深拷贝的。
我们来考虑一个问题,如果这个对象是多层的,会怎样。
const originArray = [1,[1,2,3],{a:1}]; const cloneArray = originArray.concat(); console.log(cloneArray === originArray); // false cloneArray[1].push(4); cloneArray[2].a = 2; console.log(originArray); // [1,[1,2,3,4],{a:2}]
originArray
中含有数组[1,2,3]
和对象{a:1}
,如果我们直接修改数组和对象,不会影响originArray
,但是我们修改数组[1,2,3]
或对象{a:1}
时,发现originArray
也发生了变化。结论:concat 只是对数组的第一层进行深拷贝。
slice
The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.
解释中都直接写道是 a shallow copy 了 ~
但是,并不是!
const originArray = [1,2,3,4,5]; const cloneArray = originArray.slice(); console.log(cloneArray === originArray); // false cloneArray.push(6); // [1,2,3,4,5,6] console.log(originArray); [1,2,3,4,5];同样地,我们试试多层的数组。
const originArray = [1,[1,2,3],{a:1}]; const cloneArray = originArray.slice(); console.log(cloneArray === originArray); // false cloneArray[1].push(4); cloneArray[2].a = 2; console.log(originArray); // [1,[1,2,3,4],{a:2}]果然,结果和 concat 是一样的。
结论:slice 只是对数组的第一层进行深拷贝。
Object.assign()
The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.
复制复制复制。
那到底是浅拷贝还是深拷贝呢?
自己试试吧。。
结论:Object.assign() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
... 展开运算符
const originArray = [1,2,3,4,5,[6,7,8]]; const originObj = {a:1,b:{bb:1}}; const cloneArray = [...originArray]; cloneArray[0] = 0; cloneArray[5].push(9); console.log(originArray); // [1,2,3,4,5,[6,7,8,9]] const cloneObj = {...originObj}; cloneObj.a = 2; cloneObj.b.bb = 2; console.log(originObj); // {a:1,b:{bb:2}}结论:... 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。
首层浅拷贝
我们知道了,会有一种情况,就是对目标对象的第一层进行深拷贝,然后后面的是浅拷贝,可以称作“首层浅拷贝”。
我们可以自己实现一个这样的函数:
function shallowClone(source) { const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象 for (let keys in source) { // 遍历目标 if (source.hasOwnProperty(keys)) { targetObj[keys] = source[keys]; } } return targetObj; }我们来测试一下:
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}; const cloneObj = shallowClone(originObj); console.log(cloneObj === originObj); // false cloneObj.a='aa'; cloneObj.c=[1,1,1]; cloneObj.d.dd='surprise';经过上面的修改,
cloneObj
不用说,肯定是 {a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}} 了,那 originObj 呢?刚刚我们验证了 cloneObj === originObj 是 false,说明这两个对象引用地址不同啊,那应该就是修改了 cloneObj 并不影响 originObj。console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}} console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'surprise'}}What happend?
originObj 中关于 a、c都没被影响,但是 d 中的一个对象被修改了。。。说好的深拷贝呢?不是引用地址都不一样了吗?
原来是这样:
从 shallowClone 的代码中我们可以看出,我们只对第一层的目标进行了 深拷贝 ,而第二层开始的目标我们是直接利用 = 赋值操作符进行拷贝的。
so,第二层后的目标都只是复制了一个引用,也就是浅拷贝。
总结
赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;
JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”;
JSON.stringify はディープ コピーを実装しますが、ターゲット オブジェクトに関する要件があります。
真のディープ コピーが必要な場合は、再帰してください。
この記事の事例を読んだ後は、この方法を習得したと思います。さらに興味深い情報については、php 中国語 Web サイトの他の関連記事に注目してください。
推奨読書:
以上がJSの深いコピーと浅いコピーの使い方を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。