JavaScriptの中の深いコピーと浅いコピー
9554 ワード
本論文は私の個人ブログに同期して発表します.牛とモンキー.
個人ブログのコメントシステムにメールのお知らせがあります.作者と話したいことがありますか?個人ブログにコメントしてください.
後に変化があれば、個人ブログだけで更新します.ここでは修正しないようにしてください.
深くコピーしたり、浅いコピーしたりするのもjsによくある問題です.もう一度まとめて振り返ってみます.ついでに、jsのデータの種類と保存方法を復習しました.
JavaScriptのデータタイプ
最新のECMAScript標準は、7種類のオリジナルタイプとObjectを含む8種類のデータタイプを定義しています.
オリジナルタイプ(prmitive values)
jsではObject以外のすべてのタイプは可変ではなく、これらのタイプの値は Boolean Null Udefined Number BigInt(ES 10追加) String Symbol(ES 6追加) どういう意味ですか?何が変わっていませんか
私達はjsの中で基本的なデータのタイプのいかなる操作に対しても、元の値に影響しないで、新しい値を返します.以下の例を示します
基本的なデータタイプはスタックメモリに格納された簡単なデータセグメントで、データサイズは決定され、メモリの空間サイズは割り当てられ、直接に値によって保存されるので、直接にアクセスすることができます.
オブジェクト
コンピュータ科学では、オブジェクトはメモリ内の識別子で参照できるブロック領域を指す.Javascriptでは、オブジェクトは属性のセットとして見られます.
jsのオブジェクトはスタックメモリに保存されています.オブジェクトを定義すると、変数は実際にスタックメモリに保存されているポインタです.
jsの中で対象の話題は1冊の本を書くことができて、ここはもうくどくど述べません.
浅いコピー
定義
新しいオブジェクトを作成します.このオブジェクトはオリジナルのオブジェクト属性値の正確なコピーを持っています.属性が基本タイプであれば、コピーは基本タイプの値であり、属性が参照タイプであれば、コピーはメモリアドレスであるため、オブジェクトの一つがこのアドレスを変更したら、他のオブジェクトに影響を与えます.
実現する
ES 5バージョン
ここでは簡単な実装が提供されます.
すべてはES 6の
Symbolは属性名として、この属性はfor...in、for...ofサイクルには現れません.Object.keys()、Object.getOwnPropertyNames()、JSON.strigify()には返されません.
ES 6バージョン
Object.assign()と展開演算子
これらの2つの方法はいずれも上記の
Object.keys()メソッドは、与えられたオブジェクトの自身のエニュメレート・属性からなる行列を返します.配列内の属性名の配列順と、for...inを使ってオブジェクトを巡回したときに返される順序は一致します.
上のES 5においてfor…inサイクルを通過する方法に比べて、この方法は
属性を列挙できない問題
なんですか?まだ問題がありますか?はい、まだ問題があります.何の問題ですか
ほとんどの場合、オブジェクトを巡回するには、エニュメレート・プロパティーを処理する必要はありません.特別な需要があったらどうすればいいですか? この2つの方法を組み合わせて、1つのオブジェクト自体が静的方法 コピー
定義
一つのオブジェクトをメモリから完全にコピーし、新しいエリアを開いて新しいオブジェクトを保存し、新しいオブジェクトを変更すると元のオブジェクトに影響しません.
実現する
破産版ソースオブジェクトに時間オブジェクトがある場合、時間オブジェクト ではなく、逆順序化された文字列が得られる.ソースオブジェクトにRegExp、Errerオブジェクトがある場合、プログレッシブの結果は、空のオブジェクト である.ソースオブジェクトに関数、undefinedがあると、プログレッシブ時にそれらを捨てる .ソースオブジェクトにNaN、Infinity、Infinityがあれば、プログレッシブの結果はnull になります. JSON.strigify()は、列化対象の列挙可能な自有属性 のみを有する.オブジェクトに循環参照がある場合も、深度コピー を正確に実現することができません.
パーフェクト版
以下のコードはContardLi.githb.io/deep Clon/src/clone_6.jsから来ています.コードを読むのは
コピー対象は様々な方法がありますが、実際の業務ではビジネスシーンによって最適なものを選択します.例えばオブジェクトが1つの層だけで、かつkeyとvalueが単純な文字列である場合、本明細書で述べた多くの方法は有効である.もしあなたのシーンが複雑な相手を深くコピーするなら、よく考えてみます.
本文で述べたように深くコピーしてもっと深く研究すれば、探究すべき問題がたくさん生まれます.例えば:オブジェクトタイプ判断 クローン関数 どのように正則をクローンしますか?
-…後は時間がありますので、もう少し深く研究してみましょう.
参考資料 JavaScriptデータタイプとデータ構造 あなたが知らないjsメモリとスタックメモリ ES 6時代、本当に対象をクローンできましたか? JSON.parse(JSON.strigify(obj)について、注意すべき穴を深くコピーすることを実現します。 どのように1つの驚艶な面接官の深くコピーを書き出しますか?
個人ブログのコメントシステムにメールのお知らせがあります.作者と話したいことがありますか?個人ブログにコメントしてください.
後に変化があれば、個人ブログだけで更新します.ここでは修正しないようにしてください.
深くコピーしたり、浅いコピーしたりするのもjsによくある問題です.もう一度まとめて振り返ってみます.ついでに、jsのデータの種類と保存方法を復習しました.
JavaScriptのデータタイプ
最新のECMAScript標準は、7種類のオリジナルタイプとObjectを含む8種類のデータタイプを定義しています.
オリジナルタイプ(prmitive values)
jsではObject以外のすべてのタイプは可変ではなく、これらのタイプの値は
と呼ばれています.jsのオリジナルタイプは以下の7種類があります.私達はjsの中で基本的なデータのタイプのいかなる操作に対しても、元の値に影響しないで、新しい値を返します.以下の例を示します
let str = 'nba';
str[1] = 'c';
console.log(str); // nba
上記のコードを実行すると、出力結果は依然としてnbaではなく、
です.基本的なデータタイプはスタックメモリに格納された簡単なデータセグメントで、データサイズは決定され、メモリの空間サイズは割り当てられ、直接に値によって保存されるので、直接にアクセスすることができます.
オブジェクト
コンピュータ科学では、オブジェクトはメモリ内の識別子で参照できるブロック領域を指す.Javascriptでは、オブジェクトは属性のセットとして見られます.
jsのオブジェクトはスタックメモリに保存されています.オブジェクトを定義すると、変数は実際にスタックメモリに保存されているポインタです.
jsの中で対象の話題は1冊の本を書くことができて、ここはもうくどくど述べません.
浅いコピー
定義
新しいオブジェクトを作成します.このオブジェクトはオリジナルのオブジェクト属性値の正確なコピーを持っています.属性が基本タイプであれば、コピーは基本タイプの値であり、属性が参照タイプであれば、コピーはメモリアドレスであるため、オブジェクトの一つがこのアドレスを変更したら、他のオブジェクトに影響を与えます.
実現する
ES 5バージョン
ここでは簡単な実装が提供されます.
/**
*
* @param {Object} source
*/
function copyObj(source) {
//
let result = Object.prototype.toString.call(source) === '[object Array]' ? [] : {};
for (let key in source) {
// for in , , hasOwnProperty
if (source.hasOwnProperty(key)) {
result[key] = source[key];
}
}
return result;
}
多くのプロジェクトでもこのように使われていますが、ES 6時代には本当にOKでしたか?すべてはES 6の
Symbol
タイプから話さなければなりません.ES 6において、対象の属性名は文字列またはSymbolタイプとすることができ、Symbolタイプの特徴はユニークであり、他の属性と重複しないようにすることである.しかし、それに伴う問題は:Symbolは属性名として、この属性はfor...in、for...ofサイクルには現れません.Object.keys()、Object.getOwnPropertyNames()、JSON.strigify()には返されません.
ES 6バージョン
Object.assign()と展開演算子
これらの2つの方法はいずれも上記の
Symbol
の問題を解決することができる.Object.assign()
方法は、エニュメレート・属性のすべての値を1つまたは複数のソースオブジェクトから対象オブジェクトにコピーするために使用される.ターゲットオブジェクトに戻ります.let source = { a: 1, b: {c: 2} };
let result = Object.assign({}, source);
console.log(result);
演算子を展開することで、浅いコピーも可能です.let source = { a: 1, b: {c: 2} };
let result = {...source};
console.log(result);
Object.keys()Object.keys()メソッドは、与えられたオブジェクトの自身のエニュメレート・属性からなる行列を返します.配列内の属性名の配列順と、for...inを使ってオブジェクトを巡回したときに返される順序は一致します.
上のES 5においてfor…inサイクルを通過する方法に比べて、この方法は
hasOwnProperty
の判断を回避することができる.ES 5を浅いコピーオブジェクトにする方法で、オブジェクトを遍歴してObject.keysで実現すればいいです.属性を列挙できない問題
なんですか?まだ問題がありますか?はい、まだ問題があります.何の問題ですか
私たちは、jsのオブジェクトの属性に
というマークがあることを知っています.通常、その値はtrue
です.しかし、Object.defineProperty
によって定義されるオブジェクト属性の場合、このフラグのデフォルト値はfalse
である.ほとんどの場合、オブジェクトを巡回するには、エニュメレート・プロパティーを処理する必要はありません.特別な需要があったらどうすればいいですか?
Object.getOwnPropertyNames()
は、指定されたオブジェクトのすべての自身の属性の属性名(エニュメレート・属性を含むが、名前の属性としてSymbol値を含まない)からなる行列を返します.Object.getOwnPropertySymbols()
方法は、所与のオブジェクト自体のすべてのSymbol属性の配列を返す.Symbol
属性を含む、エニュメレート・エニュメレート・エニュメレート・エニュメレート・プロパティーを得ることができる.この2つの方法を除いて:Reflect.ownKeys()
は、ターゲットオブジェクト自体の属性キーからなる配列を返す.定義
一つのオブジェクトをメモリから完全にコピーし、新しいエリアを開いて新しいオブジェクトを保存し、新しいオブジェクトを変更すると元のオブジェクトに影響しません.
実現する
破産版
let source = { a: 1, b: {c: 2} };
let result = JSON.parse(JSON.stringify());
console.log(result);
破産版のコピーが深くても使えないわけではないですが、いくつかの特別な場合には足りないです.例えば:パーフェクト版
以下のコードはContardLi.githb.io/deep Clon/src/clone_6.jsから来ています.コードを読むのは
clone
方法から直接できます.コードに注釈をつけました.
// toString
//
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';
//
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
// forEach
function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
}
//
function isObject(target) {
const type = typeof target;
return target !== null && (type === 'object' || type === 'function');
}
//
function getType(target) {
return Object.prototype.toString.call(target);
}
// - constructor
function getInit(target) {
const Ctor = target.constructor;
return new Ctor();
}
// Symbol
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
//
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
//
function cloneFunction(func) {
const bodyReg = /(?<={)(.|
)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
// prototype
if (func.prototype) {
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
if (param) {
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
function cloneOtherType(targe, type) {
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
case funcTag:
return cloneFunction(targe);
default:
return null;
}
}
function clone(target, map = new WeakMap()) {
//
if (!isObject(target)) {
return target;
}
// ,
const type = getType(target);
let cloneTarget;
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type);
} else {
return cloneOtherType(target, type);
}
//
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
// set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value, map));
});
return cloneTarget;
}
// map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value, map));
});
return cloneTarget;
}
//
const keys = type === arrayTag ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}
後ろに書いたらコピー対象は様々な方法がありますが、実際の業務ではビジネスシーンによって最適なものを選択します.例えばオブジェクトが1つの層だけで、かつkeyとvalueが単純な文字列である場合、本明細書で述べた多くの方法は有効である.もしあなたのシーンが複雑な相手を深くコピーするなら、よく考えてみます.
本文で述べたように深くコピーしてもっと深く研究すれば、探究すべき問題がたくさん生まれます.例えば:
-…後は時間がありますので、もう少し深く研究してみましょう.
参考資料