前書き
深いコピーと浅いコピーに関しては、まず 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 も変更されることがはっきりとわかります。
ディープコピー
ディープコピーは、参照のレイヤーのみをコピーする浅いコピーとは異なり、値もコピーされます。
ディープコピーが作成される限り、それらは決して相互に作用することはなく、誰も他のものに影響を与えることはありません。
ディープコピーを実装する方法は現時点ではあまり多くはありませんが、主に次の 2 つの方法があります:
JSON オブジェクトで parse と stringify を使用する
再帰を使用してオブジェクトを再作成し、各レイヤーで値を割り当てる
JSON.stringify/ parse メソッド
まず、これら 2 つのメソッドを見てみましょう:
JSON.stringify() メソッドは、JavaScript 値を JSON 文字列に変換します。
JSON.stringify は、JavaScript 値を JSON 文字列に変換します。
JSON.parse() メソッドは、JSON 文字列を解析し、その文字列で記述された JavaScript 値またはオブジェクトを構築します。
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 で理由が見つかりました:
変換中に未定義、関数、またはシンボルが見つかった場合、省略されるか (オブジェクトで見つかった場合)、null に打ち切られるか (配列で見つかった場合) のいずれかになります。 JSON.stringify は、JSON.stringify(function(){}) や JSON.stringify(unknown) のような「純粋な」値を渡す場合に、単に unknown を返すこともできます。
unknown、関数、シンボルは、変換プロセス中に無視されます。 。 。
理解してください。つまり、オブジェクトに関数 (非常に一般的) が含まれている場合、このメソッドを使用してディープ コピーを実行することはできません。
再帰メソッド
再帰の考え方は非常にシンプルで、データの各層に対してオブジェクト -> オブジェクトの代入操作を作成することです:
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'}}; 可以。那再试试带有函数的: 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: ƒ}
も利用可能です。 。終わり。
これで終わりだと思いますか? ? もちろん違います。
JavaScript の Copy メソッド
JavaScript では、配列に concat と slide という 2 つのメソッドがあり、これら 2 つのメソッドはいずれも元の配列を変更せず、変更された新しい配列を返します。
同時に、ES6 では、オブジェクトをコピーするための Object.assgn メソッドと... 展開演算子も導入されています。
それらは浅いコピーですか、それとも深いコピーですか?
concat
concat() メソッドは、2 つ以上の配列を結合するために使用されます。このメソッドは、既存の配列を変更しませんが、代わりに新しい配列を返します。
このメソッドは 2 つ以上の配列を連結できますが、そうではありません。既存の配列を変更しますが、新しい配列を返します。
意味を見るとディープコピーっぽいですね やってみましょう:
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
slice() メソッドは、配列の一部の浅いコピーを、先頭から末尾まで選択された新しい配列オブジェクトに返します (末尾は含まれません)。元の配列は変更されません。
説明は簡単です。浅いコピーとして書きました〜
でも、そうではありません!
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 と同じです。
結論: スライスは配列の最初のレベルのディープコピーを作成するだけです。
Object.assign()
Object.assign() メソッドは、列挙可能なすべての独自のプロパティの値を 1 つ以上のソース オブジェクトからターゲット オブジェクトにコピーするために使用されます。
Copyコピー コピー。
では、それは浅いコピーですか、それとも深いコピーですか?
自分で試してみてください。 。
结论: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 实现的是深拷贝,但是对目标对象有要求;
若想真正意义上的深拷贝,请递归。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
用p5.js制作烟花特效的示例代码_javascript技巧
以上が基本的な JavaScript のヒント (画像とテキストのチュートリアル、詳細な回答)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。