您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關javaScript中淺拷貝與深拷貝的區別是什么,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
1、javaScript的變量類型
(1)基本類型:
5種基本數據類型Undefined、Null、Boolean、Number 和 String,變量是直接按值存放的,存放在棧內存中的簡單數據段,可以直接訪問。
(2)引用類型:
存放在堆內存中的對象,變量保存的是一個指針,這個指針指向另一個位置。當需要訪問引用類型(如對象,數組等)的值時,首先從棧中獲得該對象的地址指針,然后再從堆內存中取得所需的數據。
JavaScript存儲對象都是存地址的,所以淺拷貝會導致 obj1 和obj2 指向同一塊內存地址。改變了其中一方的內容,都是在原來的內存上做修改會導致拷貝對象和源對象都發生改變,而深拷貝是開辟一塊新的內存地址,將原對象的各個屬性逐個復制進去。對拷貝對象和源對象各自的操作互不影響。
例如:數組拷貝
//淺拷貝,雙向改變,指向同一片內存空間 var arr1 = [1, 2, 3]; var arr2 = arr1; arr1[0] = 'change'; console.log('shallow copy: ' + arr1 + " ); //shallow copy: change,2,3 console.log('shallow copy: ' + arr2 + " ); //shallow copy: change,2,3
2、淺拷貝的實現
2.1、簡單的引用復制
function shallowClone(copyObj) { var obj = {}; for ( var i in copyObj) { obj[i] = copyObj[i]; } return obj; } var x = { a: 1, b: { f: { g: 1 } }, c: [ 1, 2, 3 ] }; var y = shallowClone(x); console.log(y.b.f === x.b.f); // true
2.2、Object.assign()
Object.assign() 方法可以把任意多個的源對象自身的可枚舉屬性拷貝給目標對象,然后返回目標對象。Object.assign會跳過那些值為 null 或 undefined 的源對象。
var x = { a: 1, b: { f: { g: 1 } }, c: [ 1, 2, 3 ] }; var y = Object.assign({}, x); console.log(y.b.f === x.b.f); // true
3、深拷貝的實現
3.1、Array的slice和concat方法
Array的slice和concat方法不修改原數組,只會返回一個淺復制了原數組中的元素的一個新數組。之所以把它放在深拷貝里,是因為它看起來像是深拷貝。而實際上它是淺拷貝。原數組的元素會按照下述規則拷貝:
如果該元素是個對象引用 (不是實際的對象),slice 會拷貝這個對象引用到新的數組里。兩個對象引用都引用了同一個對象。如果被引用的對象發生改變,則新的和原來的數組中的這個元素也會發生改變。
對于字符串、數字及布爾值來說(不是 String、Number 或者 Boolean 對象),slice 會拷貝這些值到新的數組里。在別的數組里修改這些字符串或數字或是布爾值,將不會影響另一個數組。
如果向兩個數組任一中添加了新元素,則另一個不會受到影響。例子如下:
var array = [1,2,3]; var array_shallow = array; var array_concat = array.concat(); var array_slice = array.slice(0); console.log(array === array_shallow); //true console.log(array === array_slice); //false,“看起來”像深拷貝 console.log(array === array_concat); //false,“看起來”像深拷貝
可以看出,concat和slice返回的不同的數組實例,這與直接的引用復制是不同的。而從另一個例子可以看出Array的concat和slice并不是真正的深復制,數組中的對象元素(Object,Array等)只是復制了引用。如下:
var array = [1, [1,2,3], {name:"array"}]; var array_concat = array.concat(); var array_slice = array.slice(0); array_concat[1][0] = 5; //改變array_concat中數組元素的值 console.log(array[1]); //[5,2,3] console.log(array_slice[1]); //[5,2,3] array_slice[2].name = "array_slice"; //改變array_slice中對象元素的值 console.log(array[2].name); //array_slice console.log(array_concat[2].name); //array_slice
3.2、JSON對象的parse和stringify
JSON對象是ES5中引入的新的類型(支持的瀏覽器為IE8+),JSON對象parse方法可以將JSON字符串反序列化成JS對象,stringify方法可以將JS對象序列化成JSON字符串,借助這兩個方法,也可以實現對象的深拷貝。
//例1 var source = { name:"source", child:{ name:"child" } } var target = JSON.parse(JSON.stringify(source)); target.name = "target"; //改變target的name屬性 console.log(source.name); //source console.log(target.name); //target target.child.name = "target child"; //改變target的child console.log(source.child.name); //child console.log(target.child.name); //target child //例2 var source = { name:function(){console.log(1);}, child:{ name:"child" } } var target = JSON.parse(JSON.stringify(source)); console.log(target.name); //undefined //例3 var source = { name:function(){console.log(1);}, child:new RegExp("e") } var target = JSON.parse(JSON.stringify(source)); console.log(target.name); //undefined console.log(target.child); //Object {}
這種方法使用較為簡單,可以滿足基本的深拷貝需求,而且能夠處理JSON格式能表示的所有數據類型,但是對于正則表達式類型、函數類型等無法進行深拷貝(而且會直接丟失相應的值)。還有一點不好的地方是它會拋棄對象的constructor。也就是深拷貝之后,不管這個對象原來的構造函數是什么,在深拷貝之后都會變成Object。同時如果對象中存在循環引用的情況也無法正確處理。
4、jQuery.extend()方法源碼實現
jQuery的源碼 - src/core.js #L121源碼及分析如下:
jQuery.extend = jQuery.fn.extend = function() { //給jQuery對象和jQuery原型對象都添加了extend擴展方法 var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; //以上其中的變量:options是一個緩存變量,用來緩存arguments[i],name是用來接收將要被擴展對象的key,src改變之前target對象上每個key對應的value。 //copy傳入對象上每個key對應的value,copyIsArray判定copy是否為一個數組,clone深拷貝中用來臨時存對象或數組的src。 // 處理深拷貝的情況 if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; //跳過布爾值和目標 i++; } // 控制當target不是object或者function的情況 if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // 當參數列表長度等于i的時候,擴展jQuery對象自身。 if ( length === i ) { target = this; --i; } for ( ; i < length; i++ ) { if ( (options = arguments[ i ]) != null ) { // 擴展基礎對象 for ( name in options ) { src = target[ name ]; copy = options[ name ]; // 防止永無止境的循環,這里舉個例子,如var i = {};i.a = i;$.extend(true,{},i);如果沒有這個判斷變成死循環了 if ( target === copy ) { continue; } if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : [];// 如果src存在且是數組的話就讓clone副本等于src否則等于空數組。 } else { clone = src && jQuery.isPlainObject(src) ? src : {};// 如果src存在且是對象的話就讓clone副本等于src否則等于空數組。 } // 遞歸拷貝 target[ name ] = jQuery.extend( deep, clone, copy ); } else if ( copy !== undefined ) { target[ name ] = copy;// 若原對象存在name屬性,則直接覆蓋掉;若不存在,則創建新的屬性。 } } } } // 返回修改的對象 return target; };
jQuery的extend方法使用基本的遞歸思路實現了淺拷貝和深拷貝,但是這個方法也無法處理源對象內部循環引用,例如:
var a={"name":"aaa"}; var b={"name":"bbb"}; a.child=b; b.parent=a; $.extend(true,{},a);//直接報了棧溢出。Uncaught RangeError: Maximum call stack size exceeded
5、自己動手實現一個拷貝方法
var $ = (function(){ var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' '); function type() { return Object.prototype.toString.call(this).slice(8, -1); } for (var i = types.length; i--;) { $['is' + types[i]] = (function (self) { return function (elem) { return type.call(elem) === self; }; })(types[i]); } return $; })();//類型判斷 function copy(obj,deep){ if(obj === null || typeof obj !== "object"){ return obj; } var name, target = $.isArray(obj) ? [] : {}, value; for(name in obj){ value = obj[name]; if(value === obj) { continue; } if(deep && ($.isArray(value) || $.isObject(value))){ target[name] = copy(value,deep); }else{ target[name] = value; } } return target; }
看完上述內容,你們對javaScript中淺拷貝與深拷貝的區別是什么有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。