首頁  >  文章  >  web前端  >  JavaScript基礎 詳細解讀淺拷貝和深拷貝之間的問題

JavaScript基礎 詳細解讀淺拷貝和深拷貝之間的問題

亚连
亚连原創
2018-06-01 11:21:001014瀏覽

淺拷貝和深拷貝都是對於JS中的引用類型而言的,淺拷貝就只是複製物件的引用,如果拷貝後的物件發生變化,原始物件也會發生變化。只有深拷貝才是真正地對物件的拷貝

前言

說到深淺拷貝,必須先提到的是JavaScript的資料類型,之前的一篇文章JavaScript基礎心法-資料型別說的很清楚了,這裡就不多說了。

需要知道的就是一點: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 也隨著發生了變化。

深拷貝

深拷貝就是目標的完全拷貝,不像淺拷貝那樣只是複製了一層引用,就連值也都複製了。

只要進行了深拷貝,它們老死不相往來,誰也不會影響誰。

目前實現深拷貝的方法不多,主要是兩種:

  1. 使用 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 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(){}) 或 JSON.stringify(undefined).

##' ##undefined

function

symbol

會在轉換過程中被忽略。 。 。 明白了吧,就是說如果物件中含有一個函數時(很常見),就不能用這個方法進行深拷貝。 遞歸的方法遞歸的想法就很簡單了,就是對每一層的資料都實作一次創建物件->物件賦值的操作,簡單粗暴上程式碼:<pre class="brush:js;">function deepClone(source){ const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象 for(let keys in source){ // 遍历目标 if(source.hasOwnProperty(keys)){ if(source[keys] &amp;&amp; typeof source[keys] === &amp;#39;object&amp;#39;){ // 如果值是对象,就递归一下 targetObj[keys] = source[keys].constructor === Array ? [] : {}; targetObj[keys] = deepClone(source[keys]); }else{ // 如果不是,就直接赋值 targetObj[keys] = source[keys]; } } } return targetObj; }</pre>我們來試試:

######
const originObj = {a:&#39;a&#39;,b:&#39;b&#39;,c:[1,2,3],d:{dd:&#39;dd&#39;}};
const cloneObj = deepClone(originObj);
console.log(cloneObj === originObj); // false

cloneObj.a = &#39;aa&#39;;
cloneObj.c = [1,1,1];
cloneObj.d.dd = &#39;doubled&#39;;

console.log(cloneObj); // {a:&#39;aa&#39;,b:&#39;b&#39;,c:[1,1,1],d:{dd:&#39;doubled&#39;}};
console.log(originObj); // {a:&#39;a&#39;,b:&#39;b&#39;,c:[1,2,3],d:{dd:&#39;dd&#39;}};
#########可以。那再試試有函數的:#########
const originObj = {
 name:&#39;axuebin&#39;,
 sayHello:function(){
 console.log(&#39;Hello World&#39;);
 }
}
console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = deepClone(originObj);
console.log(cloneObj); // {name: "axuebin", sayHello: ƒ}
#########也可以。搞定。 ######是不是以為這樣就完了? ?當然不是。 #########JavaScript中的拷貝方法#########我們知道在JavaScript 中,數組有兩個方法concat 和slice 是可以實現對原始數組的拷貝的,這兩個方法都不會修改原數組,而是傳回一個修改後的新數組。 ######同時,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:&#39;a&#39;,b:&#39;b&#39;,c:[1,2,3],d:{dd:&#39;dd&#39;}};
const cloneObj = shallowClone(originObj);
console.log(cloneObj === originObj); // false
cloneObj.a=&#39;aa&#39;;
cloneObj.c=[1,1,1];
cloneObj.d.dd=&#39;surprise&#39;;

经过上面的修改,cloneObj 不用说,肯定是 {a:'aa',b:'b',c:[1,1,1],d:{dd:'surprise'}} 了,那 originObj 呢?刚刚我们验证了 cloneObj === originObj 是 false,说明这两个对象引用地址不同啊,那应该就是修改了 cloneObj 并不影响 originObj。

console.log(cloneObj); // {a:&#39;aa&#39;,b:&#39;b&#39;,c:[1,1,1],d:{dd:&#39;surprise&#39;}}
console.log(originObj); // {a:&#39;a&#39;,b:&#39;b&#39;,c:[1,2,3],d:{dd:&#39;surprise&#39;}}

What happend?

originObj 中关于 a、c都没被影响,但是 d 中的一个对象被修改了。。。说好的深拷贝呢?不是引用地址都不一样了吗?

原来是这样:

  1. 从 shallowClone 的代码中我们可以看出,我们只对第一层的目标进行了 深拷贝 ,而第二层开始的目标我们是直接利用 = 赋值操作符进行拷贝的。

  2. so,第二层后的目标都只是复制了一个引用,也就是浅拷贝。

总结

  1. 赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;

  2. JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”;

  3. JSON.stringify 实现的是深拷贝,但是对目标对象有要求;

  4. 若想真正意义上的深拷贝,请递归。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

vue 简单自动补全的输入框的示例

解决vue页面刷新或者后退参数丢失的问题

解决vue单页使用keep-alive页面返回不刷新的问题

以上是JavaScript基礎 詳細解讀淺拷貝和深拷貝之間的問題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn