ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

浅谈javascript的深浅拷贝

2019-06-10 14:49:07  阅读:215  来源: 互联网

标签:console 浅谈 javascript 数据类型 var obj 拷贝 log


最近遇到了不少深浅拷贝的问题,自己也查阅了不少资料,在这里总结一下,也希望能帮助到更多有需要的小伙伴~

基本数据类型和引用数据类型

在JS中,分为两种数据类型。其中基本数据类型指的是简单的数据段,包括Number,String,Null,Undefined,Boolean五种。
引用数据类型指的是可能包含多个值的对象,比如Object,Array,函数等。
基本数据类型保存在栈内存,而引用类型保存在堆内存中。那再进一步问:为什么要分两种保存方式呢? 根本原因在于保存在栈内存的必须是大小固定的数据,引用类型的大小不固定,只能保存在堆内存中,但是我们可以把它的地址写在占内存中以供我们访问。

var a = 1;//定义了一个number类型
var obj1 = {//定义了一个objr类型
    name:'obj'
};

这段代码执行后,在内存中会是这样的:
image.png
因为这种保存方式的存在,所以我们在操作变量的时候,如果是基本数据类型,则按值访问,操作的就是变量保存的值如果是引用类型的值,我们只是通过保存在变量中的引用类型的地址类操作实际对象

基本数据类型的复制方式

var a = 1;
var b = a;//复制
console.log(b)//1
a = 2;//改变a的值
console.log(b)//1

在上面代码中,我们复制完b以后,即使改变a的值,b也不会改变,因为a和b是相互独立的,按照上面的图,也就是在栈内存中创建了一个变量b 保存的值也是2。
image.png

引用类型的复制方式

var color1 = ['red','green'];
var color2 = color1;//复制
console.log(color2)//['red','green'];
color1.push('black') ;//改变color1的值
console.log(color2)//['red','green','black']

我们只是复制了一次引用类型的地址而已,所以,不管接下来我们是操作color1还是color2,本质上都是操作同一个数组对象。
image.png

浅拷贝

如果我们想得到上面color1的数组的复制,我们可以这样:

var color1 = ['red','green']; 
var color2 = [];
//复制
for(var i  = 0;i < color1.length;i++){
    color2[i] = color1[i]; 
}
console.log(color2)//['red','green'];
color1.push('black') ;//改变color1的值
console.log(color2)//['red','green']

新建一个空的数组color2,去循环遍历数组color1中的每一项,然后把他的值复制给color2的每一项,这样color1和color2是相互独立的。同样的,对于对象,我们可以采用for in的方法去遍历每一项,得到两个互相独立的对象。

这样的方法表面上看是解决了问题,但其实还是属于浅拷贝。从直接使用 = 符号赋值进行拷贝到浅拷贝,能够拷贝成功(成功是指拷贝的结果与拷贝源完全独立),是因为我们拷贝的对象都是基本类型

function simpleClone(initalObj) {
    var obj = {};
    for ( var i in initalObj) {
        obj[i] = initalObj[i];
    }
    return obj;
}

var obj = {
    a: "hello",
    b: {
        a: "world",
        b: 21
    },
    c: ["Bob", "Tom", "Jenny"],
    d: function() {
        alert("hello world");
    }
}
var cloneObj = simpleClone(obj); // 对象拷贝
console.log(cloneObj.b); // {a: "world", b: 21}
console.log(cloneObj.c); // ["Bob", "Tom", "Jenny"]
console.log(cloneObj.d); // function() { alert("hello world"); }

// 修改拷贝后的对象
cloneObj.b.a = "changed";
cloneObj.c = [1, 2, 3];
cloneObj.d = function() { alert("changed"); };
 
console.log(obj.b); // {a: "changed", b: 21} // // 原对象所引用的对象被修改了
 
console.log(obj.c); // ["Bob", "Tom", "Jenny"] // 原对象所引用的对象未被修改
console.log(obj.d); // function() { alert("hello world"); } // 原对象所引用的函数未被修改

大家可以看上面代码,在使用for in进行循环后,我们改变cloneObj.b.a的值后,对应的 obj.b.a的值也被改变了。
其实我们在复制cloneObj.b的时候,因为他是一个对象,是一个引用数据类型,我们我们在使用=进行复制的时候,对obj.b的复制,又变成了我们前面提到的只复制了地址的情况,而没有去对对象的每一个值进行复制。

深拷贝

从上面浅拷贝的方法来看,我们可以这样来实现深拷贝:

  • 在复制基本数据类型时,我们直接使用 = 完成复制
  • 在复制所有引用类型的时候,我们循环遍历对象,对每个属性或值使用 = 完成复制

所以我们可以采用递归的方式去进行:

// 对于传入的参数,首先判断是否为引用类型,如果不是,直接返回即可;
//如果是,循环遍历该对象的属性,如果某个属性还是引用类型,则针对该属性再次调用deepCopy函数,从而完成深复制
function deepCopy (obj) {
    var result;

    //引用类型分数组和对象分别递归
    if (Object.prototype.toString.call(obj) == '[object Array]') {
      result = []
      for (i = 0; i < obj.length; i++) {
        result[i] = deepCopy(obj[i])
      }
    } else if (Object.prototype.toString.call(obj) == '[object Object]') {
      result = {}
      for (var attr in obj) {
        result[attr] = deepCopy(obj[attr])
      }
    }
    //值类型直接返回
    else {
      return obj
    }
    return result
}

Object.assign()

对于一个简单的对象,即对象中每个属性的值都是一个基本数据类型,可以使用Object.assign()完成浅拷贝。
Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。
所以它只能进行浅拷贝,当然如果目标对象只是一个简单的对象,其中每个属性的值均为基本数据类型,可以达到拷贝结果与拷贝源互不影响的效果。

Array.slice()

这个方法可以完成对数组的浅拷贝。条件同Object.assign()一样,即数组的每个元素均为基本数据类型。

JSON对象的parse和stringify

我们还可以借用JSON对象的这两个方法来实现深拷贝:

function deepClone(obj){
    let _obj = JSON.stringify(obj),
        objClone = JSON.parse(_obj);
    return objClone
}    
let a=[0,1,[2,3],4],
    b=deepClone(a);
a[0]=1;
a[2][0]=1;
console.log(a,b);

image.png

当然这个方法有一定的局限性,就是当对象中含有函数的时候,无法进行处理。

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"}

这是因为undefined、function、symbol 会在转换过程中被忽略。

参考文章:
https://segmentfault.com/a/1190000008838101
https://www.cnblogs.com/echolun/p/7889848.html

标签:console,浅谈,javascript,数据类型,var,obj,拷贝,log
来源: https://blog.csdn.net/sdfsfsdscd/article/details/91375184

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有