Javascriptはどのようにオブジェクトを深くコピーしますか?
73894 ワード
誘引子
jsでは、どのようにオブジェクトをコピーしますか?みんなの第一反応は直接に賦課文を使って評価しますか?
jsのタイプ
もともと、jsには2つのタイプの概念があり、それぞれ基本タイプと引用タイプであり、基本タイプと引用タイプの最も主要な違いはコンピュータの保存位置にあるということである.
基本タイプ:
基本的なタイプはスタックに格納され、以下の特性を有する.基本タイプの比較はそれらの値の比較 基本タイプ値をコピーすると、新たなメモリ空間が開発され、値を新たなメモリ空間にコピーする 引用タイプはヒープに保存されています.ちょっと特性があります.引用タイプの比較は住所の比較(つまりポインタが指す内容が一致しているかどうか) 参照タイプ値は、ヒープメモリに保存されているオブジェクトであり、変数が格納されているのは、そのメモリのアドレスを指すだけであり、参照タイプの値をコピーする場合は、そのメモリを指すアドレスのみがコピーされている これにより、基本型のコピーは、直接に割り当てられた語句を使用することができ、このように直接コピーすると、新しいポインタがアドレスに向けられ、新しいオブジェクトがコピーされません.新しいオブジェクトをコピーする必要がある場合、どうすればいいですか?
js対象のコピー
jsのコピーは、浅いコピーと深いコピーに分けられます.
浅いコピー:
1.Object.assign()
Object.assign()メソッドは、列挙可能な属性のすべての値を1つまたは複数のソースオブジェクトから対象オブジェクトにコピーするために使用されます.ターゲットオブジェクトに戻ります.Object.assign()は深くコピーしていないので、あえて言うならば、彼はコピー対象の第一層の基本タイプです.引用タイプはコピーしているのですか?それともポインタですか?以下の例を見ます.
3.アラy.prototype.slice()、アラy.prototype.com()
コピー:
深度コピーはすべての属性をコピーし、属性が指す動的に割り当てられたメモリをコピーします.オブジェクトが参照されているオブジェクトと一緒にコピーされると、深くコピーされます.深度コピーは、浅いコピーよりも遅く、かつ、より多くの費用がかかります.コピー前後の二つのオブジェクトは互いに影響しない.
以上のように、参照対象がポインタである以上、基本タイプは値を格納し、参照タイプを基本タイプに変更し、この基本タイプを参照タイプに変換して再割り当てすることで、参照タイプを深くコピーする効果がある.
JSON.strigifyは以下の特性を持っています.
undefined、smbol、関数の3つの場合は、直接無視されます.
実は、1つの対象の深度コピーを実現して、彼を2つの部分に分けることができて、つまり浅いコピー+再帰、現在の属性が対象かどうかを判断することができて、対象なら再帰的に操作します.
1.nullのことを考えていない
jsのデザインでは、objectの上位3桁のマークは000で、nullも32桁の表示では全部0ですので、
jsでは、typeof配列も一つのobjectであり、配列に対して処理を行う必要がある.
実は循環参照を解決するという考え方は、現在の値が既に存在しているかどうかを決める前に、循環参照を避けるために、ここではS 6のWeakMapを使ってhashテーブルを生成することができます.
4.Symbolは考えていません.
typeof Map/Setオブジェクトもobjectなので、ここでObject.prototype.toStering.callを使用する必要があります.以下、deep Clone関数を改造する必要があります.
Dateオブジェクトの複製は、直接に新しいnew Date()オブジェクトに戻り、setTime、setYearなどによる参照の変更を避けることができます.正則、および関数は参照対象ですが、山にも保存されています.ただし、一般的には属性は付けられませんので、ここでは直接に値を付与すればいいです.
上の深いコピーは再帰的に使うので、一般的に再帰的にメモリを大量に消費し、爆発的なスタックが存在することが分かります.この弊害に対して、私たちは通常二つの解決の考えがあります.一つは後戻りで、もう一つは再帰を深さ遍歴または広さに転化することです.最後に、簡単に構想を提供します.
jsでは、どのようにオブジェクトをコピーしますか?みんなの第一反応は直接に賦課文を使って評価しますか?
let a = {a: 1};
let b = a;
印刷結果を見てくださいconsole.log(a) // {a: 1}
console.log(b) // {a: 1}
プリントしたのは全部{a:1}です.いいですが、これは本当にコピーしたものですか?もう一度試してみます.a.b = 1;
console.log(a) // {a: 1, b: 1}
console.log(b) // {a: 1, b: 1}
このようにaオブジェクトだけを修正しましたが、bオブジェクトもフォローして変更されました.これは何の原因ですか?jsのタイプ
もともと、jsには2つのタイプの概念があり、それぞれ基本タイプと引用タイプであり、基本タイプと引用タイプの最も主要な違いはコンピュータの保存位置にあるということである.
基本タイプ:
Number
、String
、Boolen
、null
、undefined
・、Symbol
・、Bigint
引用タイプ:Object
・Array
・、RegExp
・・Date
・・・Function
・基本的なタイプはスタックに格納され、以下の特性を有する.
js対象のコピー
jsのコピーは、浅いコピーと深いコピーに分けられます.
浅いコピー:
, 。 , , , , , 。
浅いコピーの使用シーン:1.Object.assign()
Object.assign()メソッドは、列挙可能な属性のすべての値を1つまたは複数のソースオブジェクトから対象オブジェクトにコピーするために使用されます.ターゲットオブジェクトに戻ります.Object.assign()は深くコピーしていないので、あえて言うならば、彼はコピー対象の第一層の基本タイプです.引用タイプはコピーしているのですか?それともポインタですか?以下の例を見ます.
let obj1 = {
a: 1,
b: {c: 2}
}
let obj2 = Object.assign({}, obj1);
console.log(obj2);
// {
// a: 1,
// b: {c: 2}
// }
obj1.a = 3;
obj1.b.c = 4;
console.log(obj1);
// {
// a: 3,
// b: { c: 4}
// }
console.log(b);
// {
// a: 1,
// b: {c: 4}
// }
// }
obj1 , obj2 。 obj1 b , obj2 。
2.演算子を展開する…let obj1 = {
a: 1,
b: {c: 2}
}
let obj2 = {...obj1}
console.log(obj2); // {a: 1, b: {c: 2}}
obj1.a = 3;
obj1.b.c = 4;
console.log(obj1); // {a: 3, b: {c: 4}}
console.log(obj2); // {a: 1, b: {c: 4}}
上のコードで見た演算子を展開します.実際の効果はObject.assignと同じです.3.アラy.prototype.slice()、アラy.prototype.com()
let arr1 = [1,2,[3,4]];
let arr2 = arr1.slice(1);
console.log(arr2); // [2,[3,4]]
arr1[1] = 5;
arr1[2][0] = 6;
console.log(arr1); // [1,5,[6,4]]
console.log(arr2); // [2,[6,4]]
let arr1 = [1,[2,3]];
let arr2 = [4,5,6];
let arr3 = arr1.concat(arr2);
console.log(arr3); // [1,[2,3],4,5,6]
arr1[0] = 7;
arr1[1][0] = 8;
console.log(arr1); // [7,[8,3]]
console.log(arr3); // [1,[8,3],4,5,6]
Arayのslice
concat
方法も深くコピーされていないことが分かりますので、複雑な配列を扱う際には注意が必要です.コピー:
深度コピーはすべての属性をコピーし、属性が指す動的に割り当てられたメモリをコピーします.オブジェクトが参照されているオブジェクトと一緒にコピーされると、深くコピーされます.深度コピーは、浅いコピーよりも遅く、かつ、より多くの費用がかかります.コピー前後の二つのオブジェクトは互いに影響しない.
以上のように、参照対象がポインタである以上、基本タイプは値を格納し、参照タイプを基本タイプに変更し、この基本タイプを参照タイプに変換して再割り当てすることで、参照タイプを深くコピーする効果がある.
let a = {a: 1};
let b = JSON.stringify(a);
let c = JSON.parse(b);
console.log(a); // {a: 1}
console.log(b); // {a: 1}
a.b = 2;
console.log(a); // {a: 1, b: 2}
console.log(b) // {a: 1}
このようにして、私達は新しい相手を得ました.すべては完璧に見えますが、相手が複雑な時、また新しい問題を発見しました.let a = {
a: "1",
b: undefined,
c: Symbol("dd"),
fn: function() {
return true;
},
};
console.log(JSON.stringify(a)); // {a: 1}
emmy、aオブジェクトは3つの値があるのに、なぜJSON.stingifyの後に1つだけ現れましたか?JSON.strigifyは以下の特性を持っています.
undefined、smbol、関数の3つの場合は、直接無視されます.
let obj = {
name: 'muyiy',
a: undefined,
b: Symbol('muyiy'),
c: function() {}
}
console.log(obj); // { name: "muyiy", a: undefined, b: Symbol(muyiy), c: ƒ () }
let b = JSON.parse(JSON.stringify(obj));
console.log(b); // {name: "muyiy"}
循環参照の場合、エラーが発生します.let obj = {
a: 1,
b: {
c: 2,
d: 3
}
}
obj.a = obj.b;
obj.b.c = obj.a;
let b = JSON.parse(JSON.stringify(obj)); // Uncaught TypeError: Converting circular structure to JSON
new Dateの場合、変換結果が不正です.new Date();
// Mon Dec 24 2018 10:59:14 GMT+0800 (China Standard Time)
JSON.stringify(new Date());
// ""2018-12-24T02:59:25.776Z""
JSON.parse(JSON.stringify(new Date()));
// "2018-12-24T02:59:41.523Z"
正則を解くことができないlet obj = {
name: "muyiy",
a: /'123'/
}
console.log(obj);
// {name: "muyiy", a: /'123'/}
let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name: "muyiy", a: {}}
じゃ、私達はどうやってこのような状況を避けるべきですか?実は、1つの対象の深度コピーを実現して、彼を2つの部分に分けることができて、つまり浅いコピー+再帰、現在の属性が対象かどうかを判断することができて、対象なら再帰的に操作します.
function deepClone1(obj) {
var target = {};
for(var key in obj) {
if( typeof obj[key] === 'object') {
target[key] = deepClone1(obj[key]);
} else {
target[key] = obj[key];
}
}
return target;
}
let obj1 = {a: 1, b: {c: 2}};
let obj2 = deepClone1(obj1);
console.log(obj2); // {a: 1, b: {c: 2}}
obj1.a = 3;
obj1.b.c = 4;
console.log(obj1); // {a: 3, b: {c: 4}}
console.log(obj2); // {a: 1, b: {c: 2}}
以上は、深いコピーの簡単な実現ですが、複雑な、多種類のオブジェクトに直面して、上記の方法はまだ多くの欠陥があります.1.nullのことを考えていない
jsのデザインでは、objectの上位3桁のマークは000で、nullも32桁の表示では全部0ですので、
typeof null
もプリントアウトします.object
function deepClone2(obj) {
if (obj === null) return null; // , obj null
var target = {};
for(var key in obj) {
if( typeof obj[key] === 'object') {
target[key] = deepClone2(obj[key]);
} else {
target[key] = obj[key];
}
}
return target;
}
2.配列の互換性を考慮していません.jsでは、typeof配列も一つのobjectであり、配列に対して処理を行う必要がある.
function deepClone3(obj) {
if (obj === null) return null;
var target = Array.isArray(obj) ? []: {}; // ,
for(var key in obj) {
if( typeof obj[key] === 'object') {
target[key] = deepClone3(obj[key]);
} else {
target[key] = obj[key];
}
}
return target;
}
3.対象中の循環参照を考慮していない場合実は循環参照を解決するという考え方は、現在の値が既に存在しているかどうかを決める前に、循環参照を避けるために、ここではS 6のWeakMapを使ってhashテーブルを生成することができます.
function deepClone4(obj, hash = new WeakMap()) {
if (obj === null) return null;
if (hash.has(obj)) return hash.get(obj); // ,
var target = Array.isArray(obj) ? []: {};
hash.set(obj, target); // ,
for(var key in obj) {
if( typeof obj[key] === 'object') {
target[key] = deepClone4(obj[key], hash); // hash
} else {
target[key] = obj[key];
}
}
return target;
}
var a = {b: 1};
a.c = a;
console.log(a); // {b:1, c: {b: 1, c:{......}}}
var b = deepClone4(a);
console.log(b); // {b:1, c: {b: 1, c:{......}}}
es 5であれば、同じ配列でも可能です.4.Symbolは考えていません.
Symbol, Object.getOwnPropertySymbols() Reflect.ownKeys(), , Object.getOwnPropertySymbols() Symbol
function deepClone5(obj, hash = new WeakMap()) {
if (obj === null) return null;
if (hash.has(obj)) return hash.get(obj);
var target = Array.isArray(obj) ? []: {};
hash.set(obj, target);
// =============
let symKeys = Object.getOwnPropertySymbols(obj); //
if (symKeys.length) { //
symKeys.forEach(symKey => {
if (typeof obj[symKey] === 'object') {
target[symKey] = deepClone5(obj[symKey], hash);
} else {
target[symKey] = obj[symKey];
}
});
}
// =============
for(var key in obj) {
if( typeof obj[key] === 'object') {
target[key] = deepClone5(obj[key], hash);
} else {
target[key] = obj[key];
}
}
return target;
}
5.es 6のMapとSetのコピーtypeof Map/Setオブジェクトもobjectなので、ここでObject.prototype.toStering.callを使用する必要があります.以下、deep Clone関数を改造する必要があります.
function deepClone6(obj, hash = new WeakMap()) {
// null
if (obj === null) return null;
// hash ,
if (hash.has(obj)) return hash.get(obj);
// Symbol
let symKeys = Object.getOwnPropertySymbols(obj);
if (symKeys.length) {
symKeys.forEach(symKey =>{
if (typeof obj[symKey] === 'object') {
target[symKey] = deepClone6(obj[symKey], hash);
} else {
target[symKey] = obj[symKey];
}
});
}
// , , , ,
if (typeof obj === 'object') {
let target = null;
let result;
hash.set(obj, target);
let objType = Object.prototype.toString.call(obj);
switch (objType) {
case '[object Object]':
target = {};
break;
case '[object Array]':
target = [];
break;
case '[object Map]':
// Map
result = new Map();
obj.forEach((value, key) =>{
result.set(key, deepClone6(value, hash))
})
return result
break;
case '[object Set]':
// Set
obj.forEach((value) =>{
result.add(deepClone6(value, hash))
})
return result
break;
default:
break;
}
} else {
//
return obj;
}
for (var key in obj) {
if (typeof obj[key] === 'object') {
target[key] = deepClone6(obj[key], hash);
} else {
target[key] = obj[key];
}
}
return target;
}
6.Dateオブジェクト、正則、および関数Dateオブジェクトの複製は、直接に新しいnew Date()オブジェクトに戻り、setTime、setYearなどによる参照の変更を避けることができます.正則、および関数は参照対象ですが、山にも保存されています.ただし、一般的には属性は付けられませんので、ここでは直接に値を付与すればいいです.
function deepClone7(obj, hash = new WeakMap()) {
// null
if (obj === null) return null;
// hash ,
if (hash.has(obj)) return hash.get(obj);
// Symbol
let symKeys = Object.getOwnPropertySymbols(obj);
if (symKeys.length) {
symKeys.forEach(symKey =>{
if (typeof obj[symKey] === 'object') {
target[symKey] = deepClone7(obj[symKey], hash);
} else {
target[symKey] = obj[symKey];
}
});
}
// , , , ,
if (typeof obj === 'object' || typeof obj === 'function') {
let target = null;
let result;
hash.set(obj, target);
let objType = Object.prototype.toString.call(obj);
switch (objType) {
case '[object Object]':
target = {};
break;
case '[object Array]':
target = [];
break;
case '[object Map]':
// Map
result = new Map();
obj.forEach((value, key) =>{
result.set(key, deepClone7(value, hash))
})
return result
break;
case '[object Set]':
// Set
obj.forEach((value) =>{
result.add(deepClone7(value, hash))
})
return result
break;
case '[object Date]':
// Date
return new Date(obj)
break;
default:
// 、
return obj;
break;
}
} else {
//
return obj;
}
for (var key in obj) {
if (typeof obj[key] === 'object') {
target[key] = deepClone7(obj[key], hash);
} else {
target[key] = obj[key];
}
}
return target;
}
7.再帰的な爆発を避ける上の深いコピーは再帰的に使うので、一般的に再帰的にメモリを大量に消費し、爆発的なスタックが存在することが分かります.この弊害に対して、私たちは通常二つの解決の考えがあります.一つは後戻りで、もう一つは再帰を深さ遍歴または広さに転化することです.最後に、簡単に構想を提供します.
function cloneDeep8(x) {
const root = {};
//
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];
while(loopList.length) {
//
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// ,key undefined ,
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
//
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}