淺析javaScript中的淺拷貝和深拷貝
1、javaScript的變量類型
(1)基本類型:
5種基本數(shù)據(jù)類型Undefined、Null、Boolean、Number 和 String,變量是直接按值存放的,存放在棧內(nèi)存中的簡單數(shù)據(jù)段,可以直接訪問。
(2)引用類型:
存放在堆內(nèi)存中的對象,變量保存的是一個指針,這個指針指向另一個位置。當(dāng)需要訪問引用類型(如對象,數(shù)組等)的值時,首先從棧中獲得該對象的地址指針,然后再從堆內(nèi)存中取得所需的數(shù)據(jù)。
JavaScript存儲對象都是存地址的,所以淺拷貝會導(dǎo)致 obj1 和obj2 指向同一塊內(nèi)存地址。改變了其中一方的內(nèi)容,都是在原來的內(nèi)存上做修改會導(dǎo)致拷貝對象和源對象都發(fā)生改變,而深拷貝是開辟一塊新的內(nèi)存地址,將原對象的各個屬性逐個復(fù)制進(jìn)去。對拷貝對象和源對象各自的操作互不影響。
例如:數(shù)組拷貝
//淺拷貝,雙向改變,指向同一片內(nèi)存空間
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、淺拷貝的實現(xiàn)
2.1、簡單的引用復(fù)制
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() 方法可以把任意多個的源對象自身的可枚舉屬性拷貝給目標(biāo)對象,然后返回目標(biāo)對象。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、深拷貝的實現(xiàn)
3.1、Array的slice和concat方法
Array的slice和concat方法不修改原數(shù)組,只會返回一個淺復(fù)制了原數(shù)組中的元素的一個新數(shù)組。之所以把它放在深拷貝里,是因為它看起來像是深拷貝。而實際上它是淺拷貝。原數(shù)組的元素會按照下述規(guī)則拷貝:
- 如果該元素是個對象引用 (不是實際的對象),slice 會拷貝這個對象引用到新的數(shù)組里。兩個對象引用都引用了同一個對象。如果被引用的對象發(fā)生改變,則新的和原來的數(shù)組中的這個元素也會發(fā)生改變。
- 對于字符串、數(shù)字及布爾值來說(不是 String、Number 或者 Boolean 對象),slice 會拷貝這些值到新的數(shù)組里。在別的數(shù)組里修改這些字符串或數(shù)字或是布爾值,將不會影響另一個數(shù)組。
如果向兩個數(shù)組任一中添加了新元素,則另一個不會受到影響。例子如下:
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返回的不同的數(shù)組實例,這與直接的引用復(fù)制是不同的。而從另一個例子可以看出Array的concat和slice并不是真正的深復(fù)制,數(shù)組中的對象元素(Object,Array等)只是復(fù)制了引用。如下:
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中數(shù)組元素的值
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字符串,借助這兩個方法,也可以實現(xiàn)對象的深拷貝。
//例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格式能表示的所有數(shù)據(jù)類型,但是對于正則表達(dá)式類型、函數(shù)類型等無法進(jìn)行深拷貝(而且會直接丟失相應(yīng)的值)。還有一點不好的地方是它會拋棄對象的constructor。也就是深拷貝之后,不管這個對象原來的構(gòu)造函數(shù)是什么,在深拷貝之后都會變成Object。同時如果對象中存在循環(huán)引用的情況也無法正確處理。
4、jQuery.extend()方法源碼實現(xiàn)
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對應(yīng)的value。
//copy傳入對象上每個key對應(yīng)的value,copyIsArray判定copy是否為一個數(shù)組,clone深拷貝中用來臨時存對象或數(shù)組的src。
// 處理深拷貝的情況
if ( typeof target === "boolean" ) {
deep = target;
target = arguments[1] || {};
//跳過布爾值和目標(biāo)
i++;
}
// 控制當(dāng)target不是object或者function的情況
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// 當(dāng)參數(shù)列表長度等于i的時候,擴展jQuery對象自身。
if ( length === i ) {
target = this;
--i;
}
for ( ; i < length; i++ ) {
if ( (options = arguments[ i ]) != null ) {
// 擴展基礎(chǔ)對象
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// 防止永無止境的循環(huán),這里舉個例子,如var i = {};i.a = i;$.extend(true,{},i);如果沒有這個判斷變成死循環(huán)了
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存在且是數(shù)組的話就讓clone副本等于src否則等于空數(shù)組。
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};// 如果src存在且是對象的話就讓clone副本等于src否則等于空數(shù)組。
}
// 遞歸拷貝
target[ name ] = jQuery.extend( deep, clone, copy );
} else if ( copy !== undefined ) {
target[ name ] = copy;// 若原對象存在name屬性,則直接覆蓋掉;若不存在,則創(chuàng)建新的屬性。
}
}
}
}
// 返回修改的對象
return target;
};
jQuery的extend方法使用基本的遞歸思路實現(xiàn)了淺拷貝和深拷貝,但是這個方法也無法處理源對象內(nèi)部循環(huán)引用,例如:
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、自己動手實現(xiàn)一個拷貝方法
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;
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Javascript 淺拷貝、深拷貝的實現(xiàn)代碼
- JavaScript數(shù)組深拷貝和淺拷貝的兩種方法
- 淺談JavaScript中面向?qū)ο蟮牡纳羁截惡蜏\拷貝
- js對象淺拷貝和深拷貝詳解
- javascript對淺拷貝和深拷貝的詳解
- javascript深拷貝和淺拷貝詳解
- JavaScript基礎(chǔ)心法 深淺拷貝(淺拷貝和深拷貝)
- javascript深拷貝、淺拷貝和循環(huán)引用深入理解
- JavaScript深拷貝和淺拷貝概念與用法實例分析
- JavaScript實現(xiàn)淺拷貝與深拷貝的方法分析
- JS淺拷貝和深拷貝原理與實現(xiàn)方法分析
- JS中實現(xiàn)淺拷貝和深拷貝的代碼詳解
- javascript二維數(shù)組和對象的深拷貝與淺拷貝實例分析
- javascript 關(guān)于賦值、淺拷貝、深拷貝的個人理解
- JS賦值、淺拷貝和深拷貝(數(shù)組和對象的深淺拷貝)實例詳解
- 詳解JS深拷貝與淺拷貝
- JS對象復(fù)制(深拷貝和淺拷貝)
- js深拷貝與淺拷貝一文徹底搞懂
相關(guān)文章
微信小程序movable view移動圖片和雙指縮放實例代碼
movable-area是微信小程序的新組件,可以用來移動視圖區(qū)域movable-view。這篇文章主要介紹了微信小程序movable view移動圖片和雙指縮放實例代碼,需要的朋友可以參考下2017-08-08
淺談js中Object.create()與new的具體實現(xiàn)與區(qū)別
本文主要介紹了js中Object.create()與new的具體實現(xiàn)與區(qū)別,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
BootStrap Validator 版本差異問題導(dǎo)致的submitHandler失效問題的解決方法
這篇文章主要介紹了BootStrap Validator 版本差異問題導(dǎo)致的submitHandler失效問題的解決方法,下面通過本文給大家詳細(xì)說明一下,需要的朋友可以參考下2016-12-12
js使用循環(huán)清空某個div中的input標(biāo)簽值
清空div中input標(biāo)簽值的方法有很多,下面為大家介紹下使用循環(huán)清空某個div中的input標(biāo)簽值的具體實現(xiàn)2014-09-09
js實現(xiàn)權(quán)限樹的更新權(quán)限時的全選全消功能
上一篇發(fā)了添加權(quán)限時的權(quán)限樹JS源碼,下面把更新時的也發(fā)給大家借鑒一下,因為更新時候牽扯到判斷已有權(quán)限等,所以,還要麻煩一些。2009-02-02

