JavaScriptは浅いコピーと深いコピーです.

6587 ワード

データの種類
コピーを開始する前に、私たちはJavaScriptのデータタイプとメモリの保存先から話します.データタイプは、基本データタイプと参照データタイプに分けられます.
基本的なデータタイプは主にundefined、bollan、number、string、nullを含みます.
基本的なデータタイプは主にスタックに格納され、スタックに格納されるデータは簡単で、サイズは決定される.スタックメモリに保存されているデータは直接に値で保存されており、直接にアクセスすることができます.
基本的なデータタイプの比較は値の比較であり、それらの値が等しい限りそれらは等しいと考えられる.
let a = 1;
let b = 1;
console.log(a === b); //true
ここでは厳密に等しいものを使っています.
let a = true;
let b = 1;
console.log(a == b); //true
参照データタイプとは、オブジェクトタイプのObject typeで、例えば、Object、array、function、dateなどです.参照データの種類は***ヒープ*メモリに保存されています.変数は実際にスタックメモリに保存されているポインタです.このポインタはヒープメモリのアドレスを指しています.
参照データのタイプの比較は参照の比較です.だから、私たちはjsの参照タイプを操作するたびに、スタックメモリに保存されているポインタを操作するので、2つの参照データのタイプを比較します.これらのポインタが同じオブジェクトを指すかどうかを見ます.
let foo = {a: 1, b: 2};
let bar = {a: 1, b: 2};
console.log(foo === bar); //false
変数fooと変数barは同じ内容を表していますが、メモリ内の位置が異なります.つまり、変数fooとbarはスタックメモリに保存されているポインタが指すのは、スタックメモリ内の同じオブジェクトではないので、それらは同じではないです.
スタックとスタックの違い
浅いコピーの和深コピーの主な違いは、データがメモリに保存されているタイプが違うことです.スタックとスタックはメモリの中で区分して記憶する領域です.スタック(stack)は自動的に割り当てられたメモリ空間であり、システムによって自動的に解放される.ヒープはダイナミックに割り当てられたメモリであり、サイズも自動的に解放されない.
浅いコピー
あなたの対象が値タイプの属性しかないなら、ES 6の新しい文法Object.assignでコピーができます.
//   
let obj = {foo: 'foo', bar: "bar"};

let shallowCopy = { ...obj };  //{foo: 'foo', bar: "bar"}
//   
let obj = {foo: "foo", bar: "bar"};

let shallowCopy = Object.assign({}, obj); //{foo: 'foo', bar: "bar"}
私達は次に浅いコピーと賦(=)の違いを見に行きます.
let obj = {foo: "foo", bar: "bar"};
let shallowCopy = { ...obj }; //{foo: 'foo', bar: "bar"}
let obj2= obj; //{foo: 'foo', bar: "bar"}
shallowCopy.foo = 1;
obj2.bar = 1
console.log(obj); //{foo: "foo", bar: 1};
与えられたobj 2と最初のobjが同じ対象を指していることが分かります.データを変えると元のデータが一緒に変わります.浅いコピーで得られたsharowCopyは、Objの第1層のデータオブジェクトをコピーして、ソースデータと同じ対象に向けず、変更は元のデータと一緒に変更されない.
コピー
しかし、Object.assignメソッドは対象の1階のコピーしかできません.対象の属性は対象のオブジェクトで、彼は深層コピーができません.迷ったでしょう?直接コード解釈
let foo = {a: 0, b: {c: 0}};
let copy = { ...foo };
copy.a = 1;
copy.b.c = 1;
console.log(copy); //{a: 1, b: {c: 1}};
console.log(foo); //{a: 0, b: {c: 1}};
Object.assign(...)メソッドを使ってコピーしたCopyオブジェクトの二層のオブジェクトが変更されたときも、元のデータと一緒に変更されることが見られます.ここで、サブオブジェクトが存在するオブジェクトをコピーするときは、深くコピーします.
浅いコピー:BオブジェクトをAオブジェクトにコピーし、Bの中のサブオブジェクトを含まない.サブコピー:BオブジェクトをAオブジェクトにコピーし、Bの中のサブオブジェクトを含む.
深度コピーの実現方法:
ここではいくつかの常用方法を言います.1.JSON.parse(JSON.strigify();
	let foo = {a: 0, b: {c: 0}};
   	let copy = JSON.parse(JSON.stringify(foo));
   	copy.a = 1;
   	copy.b.c = 1;

   	console.log(copy); //{a: 1, b: {c: 1}};
   	console.log(foo); //{a: 0, b: {c: 0}};
2.再帰的にコピーする
	function deepCopy(initialObj, finalObj){
    		let obj = finalObj || {};
    		for(let i in initialObj) {
    			if(typeof initialObj[i] === "object") {
    				obj[i] = (initialObj[i].constructor === Array) ? [] : {};
    				arguments.callee(initialObj[i], obj[i]);
    			}else{
    				obj[i] = initialObj[i];
    			}
    		}
    		return obj;
    	}


    	var foo = {a: 0, b: {c: 0}};
    	var str = {};

    	deepCopy(foo, str);

    	str.a = 1;
    	str.b.c = 1;

    	console.log(str); //{a: 1, b: {c: 1}};
    	console.log(foo); //{a: 0, b: {c: 0}};
上記のコードは確かに深いコピーが可能ですが、相互参照の対象が二つあるとデッドサイクルが発生します.
相互参照の対象がデッドサイクルにつながることを避けるためには、巡回中に相互参照の対象があるかどうかを判断し、ループを終了します.
改良版コードは以下の通りです.
		function deepCopy(initialObj, finalObj) {
			let obj = finalObj || {};
			for(let i in initialObj){
				let prop = initialObj[i];//           , initialObj.a = initialObj   
				if(prop === obj) {
					continue;
				}
				if(typeof prop === 'object'){
					obj[i] = (prop.constructor === Array) ? [] : {};
					arguments.callee(prop, obj[i]);
				}else{
					obj[i] = prop;
				}
			}
			return obj;
		}


    	var foo = {a: 0, b: {c: 0}};
    	var str = {};

    	deepCopy(foo, str);

    	str.a = 1;
    	str.b.c = 1;

    	console.log(str); //{a: 1, b: {c: 1}};
    	console.log(foo); //{a: 0, b: {c: 0}};
3.Object.creat()を使用する方法
		function deepCopy(initialObj, finalObj){
    		let obj = finalObj || {};
    		for(let i in initialObj){
    			let prop = initialObj[i];  //             , initialObj[i].a = initialObj   
    			if(prop === obj){
    				continue;
    			}
    			if(typeof prop === "object"){
    				obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
    			}else{
    				obj[i] = prop;
    			}
    		}
    		return obj;
    	}


    	let foo = {a: 0, b: {c: 0}};
    	let str = {};

    	deepCopy(foo, str);

    	str.a = 1;
    	str.b.c = 1;

    	console.log(str); //{a: 1, b: {c: 1}};
    	console.log(foo); //{a: 0, b: {c: 0}};
4.jQuery
jQueryは1つの$を提供しました.extedは深くコピーすることができます.
var $ = require('jquery);

let foo = {a: 0, b: {c: 0}};
let str = $.extend(true, {}, foo);
str.a = 1;
str.b.c = 1;

console.log(str); //{a: 1, b: {c: 1}};
console.log(foo); //{a: 0, b: {c: 0}};

5.lodashのもう一つの人気の関数ライブラリlodashは、_.clone Deepは深コピーに使います.
var _ = require('lodash);

let foo = {a: 0, b: {c: 0}};
let str = _.cloneDeep(foo);
str.a = 1;
str.b.c = 1;

console.log(str); //{a: 1, b: {c: 1}};
console.log(foo); //{a: 0, b: {c: 0}};
限定的にすべての深いコピーの方法は、すべてのタイプのオブジェクトには適用されません.もちろん他のピットもありますが、原型チェーンの属性はどうやってコピーしますか?コピーの仕方は列挙できない属性などです.lodashは最も安全な汎用的な深度コピーの方法ですが、もしあなたが自分で着手するなら、必要に応じてあなたに最適なより効率的な深度コピーの方法を書き出すかもしれません.
//              
		function deepCopy(obj) {
    		let copy;

    		//               undefined null
    		if(obj == null || typeof obj != "object") return obj;

    		//  Date
    		if(obj instanceof Date){
    			copy = new Date();
    			copy.setTime(obj.getTime());
    			return copy;
    		}

    		//  Array
    		if(obj instanceof Array) {
    			copy = [];
    			for(let i = 0; i < obj.length; i++) {
    				copy[i] = deepCopy(obj[i]);
    			}
    			return copy;
    		}

    		//  Function
    		if(obj instanceof Function) {
    			copy = function() {
    				return obj.apply(this, arguments);
    			}
    			return copy;
    		}

    		//  Object
    		if(obj instanceof Object) {
    			copy = {};
    			for(let attr in obj) {
    				if(obj.hasOwnProperty(attr)) copy[attr] = deepCopy(obj[attr]);
    			}
    			return copy;
    		}


    		throw new Error("     " +obj.constructor+ "     ")
    	}
コピー