articles icon indicating copy to clipboard operation
articles copied to clipboard

Javascript深浅拷贝

Open Wscats opened this issue 8 years ago • 28 comments

Javascript有六种基本数据类型(也就是简单数据类型),它们分别是:Undefined,Null,Boolean,Symbol,Number和String。还含有一种复杂数据类型,就是对象

注意Undefined和Null的区别,Undefined类型只有一个值,就是undefined,Null类型也只有一个值,也就是null Undefined其实就是已声明未赋值的变量输出的结果 null其实就是一个不存在的对象的结果

var c;
console.log(c)//undefined

console.log(document.getElementById('wsscat'))//没有id为wsscat的节点,输出null

简单的数据类型和复杂的数据类型有以下重要的区别

对于简单数据类型

它们值在占据了内存中固定大小的空间,并被保存在栈内存中。当一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本,还有就是不能给基本数据类型的值添加属性

var a = 1;
var b = a;
a.attr = 'wsscat';
console.log(a.attr)//undefined

上面代码中a就是简单数据类型(Number),b就是a的副本,它们两者都占有不同位置但相等的内存空间

对于复杂的数据类型

复杂的数据类型即引用类型,它的值是对象,保存在堆内存中,包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。

var obj = {
    name:'wsscat',
    age:0
}
var obj2 = obj;
obj2['c'] = 5;
console.log(obj);//Object {name: "wsscat", age: 0, c: 5}
console.log(obj2);////Object {name: "wsscat", age: 0, c: 5}

qq20161016-0

我们可以看到obj赋值给obj2后,当我们更改其中一个对象的属性值,两个对象都发生了改变,究其原因局势因为obj和obj2这两个变量都指向同一个指针,赋值只是复制了指针,所以当我们改变其中一个的值就会影响另外一个变量的值

浅拷贝

其实这段代码就是浅拷贝,有时候我们只是想备份数组,但是只是简单让它赋给一个变量,改变其中一个,另外一个就紧跟着改变,但很多时候这不是我们想要的

var obj = {
    name:'wsscat',
    age:0
}
var obj2 = obj;
obj2['c'] = 5;
console.log(obj);//Object {name: "wsscat", age: 0, c: 5}
console.log(obj2);////Object {name: "wsscat", age: 0, c: 5}

深拷贝

数组 对于数组我们可以使用slice()concat()方法来解决上面的问题 slice

var arr = ['wsscat', 'autumns', 'winds'];
var arrCopy = arr.slice(0);
arrCopy[0] = 'tacssw'
console.log(arr)//['wsscat', 'autumns', 'winds']
console.log(arrCopy)//['tacssw', 'autumns', 'winds']

concat

var arr = ['wsscat', 'autumns', 'winds'];
var arrCopy = arr.concat();
arrCopy[0] = 'tacssw'
console.log(arr)//['wsscat', 'autumns', 'winds']
console.log(arrCopy)//['tacssw', 'autumns', 'winds']

对象 对象我们可以定义一个新的对象并遍历新的属性上去实现深拷贝

var obj = {
    name:'wsscat',
    age:0
}

var obj2 = new Object();
obj2.name = obj.name;
obj2.age = obj.age

obj.name = 'autumns';
console.log(obj);//Object {name: "autumns", age: 0}
console.log(obj2);//Object {name: "wsscat", age: 0}

当然我们可以封装好一个方法来处理对象的深拷贝,代码如下

var obj = {
    name: 'wsscat',
    age: 0
}
var deepCopy = function(source) {
    var result = {};
    for(var key in source) {
        if(typeof source[key] === 'object') {
            result[key] = deepCopy(source[key])
        } else {
            result[key] = source[key]
        }
    }
    return result;
}
var obj3 = deepCopy(obj)
obj.name = 'autumns';
console.log(obj);//Object {name: "autumns", age: 0}
console.log(obj3);//Object {name: "wsscat", age: 0}

Wscats avatar Oct 13 '16 06:10 Wscats

Javascript数组存放函数

在javascript中函数也是一种数据,能够像操作一个对象对它进行操作。并且javascript不进行数据类型检查,数组可以存放任何东西,在下面代码中我们不但在数组中存放了函数,并且也可以在存放一个执行函数的返回值,所以数组前两个数据存放都是函数执行返回值

var funcA = function() {
    console.log("funcA");
    return "hello funA";
}
var funcB = function() {
    console.log("funcB");
    return "hello funB";
}
var funcC = function() {
    console.log("funcC");
    return "hello funC";
}
var arr = [funcA(), funcB(), funcC];
console.log(arr);
arr[2]();

输出的结果如下 qq20161105-0

Wscats avatar Nov 05 '16 11:11 Wscats

对象中不含有函数的话。JSON解析反解析就行了

dcy0701 avatar Dec 16 '16 05:12 dcy0701

深拷贝

数组 对于数组我们可以使用slice()和concat()方法来解决上面的问题 silce ...

应该是手滑了吧,silce -> slice

ghost avatar Dec 18 '16 01:12 ghost

谢谢,已更正~

Wscats avatar Dec 18 '16 10:12 Wscats

ES6中有 Object.assign() 方法,应该也可以解决你对于深克隆的需求吧?

GreenMelon avatar Dec 18 '16 15:12 GreenMelon

image concat 和slice对对象数组没有效果的?

HeftyKoo avatar Dec 22 '16 02:12 HeftyKoo

@GreenMelon Object.assign()方法不适于深拷贝,MDN上有说明

flynntsc avatar Dec 28 '16 01:12 flynntsc

@yeyuqiudeng concat和slice主要是针对基本数据类型数组的深拷贝,牵涉到多层复杂数据类型即对象的就要再对{a:{b:1}进行深拷贝。有兴趣的话,可对Underscore、Lodash 和 JQuery的源码进行学习,相关文章《深入剖析 JavaScript 的深复制

flynntsc avatar Dec 28 '16 02:12 flynntsc

@flynntsc 嗯嗯,谢谢

HeftyKoo avatar Dec 28 '16 02:12 HeftyKoo

我这里实现了对象包括数组的深拷贝:

Object.prototype.deepCopy=function(){
    var obj=null;//用于最后返回一个对象,这个对象是深复制的结果
    for(var attr in this){//遍历这个对象的每一个属性
        if(this.hasOwnProperty(attr)){//主要是递归自有属性
            if(typeof (this[attr]==='object')){//如果对象的属性是一个对象,就递归复制它的每一个属性
                if(this[attr]===null){//如果对象为null
                    obj[attr]=null;
                }else if(Object.prototype.toString(this[attr])==='[object Array]'){//如果是个数组
                    obj[attr]=[];
                    for(var i=0;i<this[attr].length;i++){
                        obj[attr].push(this[attr][i].deepCopy());
                    }
                }else{//object
                    obj[attr]=this[attr].deepCopy();
                }
            }else{
                obj[attr]=this[attr];
            }
        }
    }
    return obj;
}

specialCoder avatar Jan 17 '17 03:01 specialCoder

test.html:12 Uncaught RangeError: Maximum call stack size exceeded 报错了 @specialCoder

Joephy2012 avatar Mar 02 '17 10:03 Joephy2012

基本类型在ES6还有一个Symbol,漏掉了

嗯嗯 by @Wscats

radicalviva avatar Mar 30 '17 15:03 radicalviva

不错不错👍🏻

jiao852jiujiu avatar Jun 17 '17 03:06 jiao852jiujiu

写得挺好的

kokpapa avatar Sep 05 '17 23:09 kokpapa

涨知识啦

huchenh avatar Oct 09 '17 09:10 huchenh

没看到对循环引用的处理啊

yunbiji avatar Mar 30 '18 12:03 yunbiji

function deepCopy(jsonData){ return JSON.parse(JSON.stringify(jsonData));
}; 这样也可以吧

plane-hjh avatar Apr 01 '18 15:04 plane-hjh

@plane-hjh 这样有其他问题 具体可以看我 这篇文章

LiuL0703 avatar Apr 08 '18 11:04 LiuL0703

@kscript 循环引用解决方案可以看这个具体可以看这篇文章

LiuL0703 avatar Apr 08 '18 11:04 LiuL0703

slice、concat、assign都是浅拷贝,如果数据中没有函数,可以直接用JSON来实现拷贝,如果有函数,就得用递归来实现深拷贝,递归实现时,函数依然是公用的; 更高级的可以参考楼上的 文章https://github.com/LiuL0703/blog/issues/19

AdamssBryan avatar Apr 10 '18 08:04 AdamssBryan

我这里实现了对象包括数组的深拷贝:

Object.prototype.deepCopy=function(){
    var obj=null;//用于最后返回一个对象,这个对象是深复制的结果
    for(var attr in this){//遍历这个对象的每一个属性
        if(this.hasOwnProperty(attr)){//主要是递归自有属性
            if(typeof (this[attr]==='object')){//如果对象的属性是一个对象,就递归复制它的每一个属性
                if(this[attr]===null){//如果对象为null
                    obj[attr]=null;
                }else if(Object.prototype.toString(this[attr])==='[object Array]'){//如果是个数组
                    obj[attr]=[];
                    for(var i=0;i<this[attr].length;i++){
                        obj[attr].push(this[attr][i].deepCopy());
                    }
                }else{//object
                    obj[attr]=this[attr].deepCopy();
                }
            }else{
                obj[attr]=this[attr];
            }
        }
    }
    return obj;
}

typeof 对array和object都返回object,所以没有必要加一层array的判断吧?

refanbanzhang avatar Oct 11 '18 09:10 refanbanzhang

function deepCopy(jsonData){ return JSON.parse(JSON.stringify(jsonData)); }; 这样也可以吧 只适合对象吧这个

zpj4206 avatar Nov 28 '18 02:11 zpj4206

js中函数也是一种引用类型数据,a,b指向两个不同的函数,执行的结果也是有区别的发自我的华为手机-------- 原始邮件 --------主题:Re: [Wscats/Good-Text-Share] Javascript深浅拷贝 (#57)发件人:燕睿涛 收件人:Wscats/Good-Text-Share 抄送:陈虎 ,Comment function() {}也算基本数据类型吗

let a = function() {console.log(123);}

let b = a

b = function() {console.log(456);}

执行上面只有,a、b是不同的两个函数,按理说,函数应该存储的也是,求解

—You are receiving this because you commented.Reply to this email directly, view it on GitHub, or mute the thread.

huchenh avatar Dec 11 '18 14:12 huchenh

let deep_copy = function f(obj) {
    let new_value;
    if(typeof obj === 'object' && obj != null){
        if(obj instanceof Array){
            new_value = [];
            for(let i=0;i<obj.length;i++){
                if(typeof obj[i] !== 'object' || obj[i] === null){
                    new_value[i] = obj[i];
                }else{
                    new_value.push(f(obj[i]));
                }
            }
        }else{
            new_value = {};
            for(let item in obj){
                if(obj.hasOwnProperty(item)){
                    if(typeof obj[item] !== 'object' || obj[item] === null){
                        new_value[item] = obj[item];
                    }else{
                        Object.assign(new_value, { [item]: f(obj[item])});
                    }
                }
            }
        }
    }else{
        new_value = obj;
    }
 
    return new_value;
};

tanf1995 avatar Jan 12 '19 05:01 tanf1995

我觉的你的理解有点问题,对象的赋值操作不能算是浅拷贝。在对对象进行浅拷贝时,对象中的基本数据类型会开辟新的空间,引用类型指向的还是同一个地址。而赋值操作,是将对象B的地址指向对象A。 举个例子

var obj1 = {
      name: 'zhangsan',
      num: [1, 2, 3]
}
// 赋值操作
var obj2 = obj1
obj2.name = 'lisi'
console.log(obj1) // {name: "lisi", num: [1, 2, 3] }
console.log(obj2) // {name: "lisi", num: [1, 2, 3] }
// 浅拷贝
var obj3 = Object.assign({}, obj1)
obj3.name = 'wanger'
console.log(obj1) // {name: "lisi", num: [1, 2, 3] }
console.log(obj3) // {name: "wanger", num: [1, 2, 3] }

ZSH0A0 avatar Feb 13 '19 09:02 ZSH0A0

我同你认知的一样啊,将引用类型直接赋值给其他变量,只是传递了内存地址。

获取 Outlook for Androidhttps://aka.ms/ghei36


From: gudongz [email protected] Sent: Wednesday, February 13, 2019 5:47:45 PM To: Wscats/Good-Text-Share Cc: 谭峰; Comment Subject: Re: [Wscats/Good-Text-Share] Javascript深浅拷贝 (#57)

我觉的你的理解有点问题,对象的赋值操作不能算是浅拷贝。在对对象进行浅拷贝时,对象中的基本数据类型会开辟新的空间,引用类型指向的还是同一个地址。而赋值操作,是将对象B的地址指向对象A。 举个例子 var obj1 = { name: 'zhangsan', num: [1, 2, 3] } // 赋值操作 var obj2 = obj1 obj2.name = 'lisi' console.log(obj1) // {name: "lisi", num: [1, 2, 3] } console.log(obj2) // {name: "lisi", num: [1, 2, 3] } // 浅拷贝 var obj3 = Object.assign({}, obj1) obj3.name = 'wanger' console.log(obj1) // {name: "lisi", num: [1, 2, 3] } console.log(obj3) // {name: "wanger", num: [1, 2, 3] }

― You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://github.com/Wscats/Good-Text-Share/issues/57#issuecomment-463131186, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AayVkmZtc8X140WL6dPazBDB1MYAYOLFks5vM99BgaJpZM4KViK-.

tanf1995 avatar Feb 21 '19 04:02 tanf1995

如果需要深拷贝 函数和正则 呢

Rotten-Egg avatar Nov 27 '19 08:11 Rotten-Egg

ES6中有 Object.assign() 方法,应该也可以解决你对于深克隆的需求吧?

assign不是深克隆

zhelingwang avatar Jul 23 '20 10:07 zhelingwang