JS深浅拷贝

早上好呀!今天向大家分享一下JS里的拷贝!之前也是看过许多关于拷贝的文章,最后还是总结,想要分享给大家啦!
JS拷贝分为深浅拷贝!

一 js数据类型

js数据有不同的划分方式,此处以数据在内存中的储存形式划分,可以分为两种类型:基本类型和复杂类型

  1. 基本类型主要包括:string,number,boolean,undefined,null,symbol
  2. 复杂类型主要包括:object,array,function

二 js中的栈堆

在js引擎中对数据的存储主要有两种位置,堆内存和栈内存。

栈内存主要用于存储各种基本类型的变量,包括string,number,boolean,undefined,null,symbol,以及复杂类型的指针。

而堆内存主要负责复杂数据(object,array,function)的内容部分的存储。


栈内存和堆内存

  1. 由系统自动分配释放;
  2. 一般有固定大小和范围上限;
    3.存放基本数据或复杂数据的指针等;
    4.读取顺序是先进后出。

  1. 一般由程序员分配释放, 若程序员不释放,程序结束时可能由系统回收;
  2. 存储大小未知,由程序执行时产生的数据大小自动确定;
  3. 用于存放复杂数据;
  4. 读取顺序是先进先出。

三 数据的拷贝

数据拷贝,一般指栈堆中的栈拷贝。如果是基本数据,那么拷贝的就是数据;如果是复杂数据,那么拷贝的是指针,可能会导致多个指针指向同一个对象。

具体体现为,基本数据拷贝后,修改拷贝后的数据,不会被影响原数据;复杂数据拷贝后,修改拷贝后的数据,会作用到原数据,因为此时修改的是同一个指针指向的数据。

1. 基本数据的拷贝

var a = 10;
var b = a;
console.log(a);  // 10
console.log(b);  // 10

var c = 10;
var d = c;       // 拷贝的是数据
d = 20;          // 修改拷贝之后的数据
console.log(c);  // 10
console.log(d);  // 20

2. 复杂数据的拷贝

var obj = { a: 10 };
var obj2 = obj;
console.log(obj);  // { a: 10 }
console.log(obj2); // { a: 10 }

var obj = { a: 10 };
var obj2 = obj;    // 拷贝的只是指针,不是数据
obj2.a = 20;       // 修改拷贝之后的数据
console.log(obj);  // { a: 20 }
console.log(obj2); // { a: 20 } 

四 对象的拷贝

所谓对象的拷贝,其实就是基于复杂数据在拷贝时的异常处理,我们将复杂数据的默认拷贝定义为浅拷贝;就是只拷贝复杂数据的地址,而不拷贝值。那么与之对应的就是不拷贝地址,只拷贝值,也被称为深拷贝。

一般情况下,等号赋值,函数传参,都是浅拷贝,也就是只拷贝了数据的地址。

let foo = {
    title:"hello obj"
}

// 等号赋值
let now = foo;
now.title = "this is new title";

console.log(foo.title);       // this is new title
console.log(now.title);       // this is new title

// 函数传参
function change(o){
    o.title = "this is function change title";
}
change(foo);
console.log(foo.title);       // this is function change title

如果要实现拷贝数据的值,需要借助一些措施,或处理方式。如:

1. 遍历复杂数据,拷贝内部属性或值

let foo = {
    title:"hello obj"
}

let now = {};
for(let attr in foo){
    now[attr] = foo[attr];
}
// 深拷贝成功
console.log(foo === now);     // false
now.title = "this is new title";

console.log(foo.title);       // hello obj
console.log(now.title);       // this is new title

这种方式比较复杂,并且在遇到多层复杂数据嵌套时,只能深拷贝一层数据,如果修改内层复杂数据,依然会影响原数据,无法解决本质问题

let foo = {
    title:{
        sTitle:"this is small title"
    }
}
let now = {};
for(let attr in foo){
  now[attr] = foo[attr];
}
// 外层对象深拷贝成功
console.log(foo === now);                // false
// 内层对象依然是浅拷贝
console.log(foo.title === now.title);    // true 
now.title.sTitle = "this is new small title";

console.log(foo.title.sTitle);           // this is new small title
console.log(now.title.sTitle);           // this is new small title
但可以利用函数递归的方式解决这个问题 ---- 强烈推荐
function deepClone(target){
    // 判断要处理的目标是数组还是对象
    let newObj = target.constructor === Array ? [] : {};
    // 遍历目标:in运算符会拿到原型上继承到的属性
    for (let attr in target){
        // 判断属性是否在当前对象自身
        if (target.hasOwnProperty(attr)) {
            // 如果值是对象或数组,就递归一下
            if (target[attr] && typeof target[attr] === "object") {
                newObj[attr] = target[attr].constructor === Array ? [] : {};
                newObj[attr] = deepClone(target[attr]);
            }else if(typeof target[attr] === "function"){
                // 如果是函数,就复制一个新函数
                newObj[attr] = target[attr].bind(target);
            }else{
                // 如果不是对象,数组,函数,就直接赋值
                newObj[attr] = target[attr];
            }
        }
    }
    return newObj;
}

// 测试
let foo = {
    title:{
        sTitle:"this is small title",
        show:function(){
            console.log(this);
        },
        list:["hello","world",{a:10,b:20}]
    }
}

let now = deepClone(foo);

// 外层对象深拷贝成功
console.log(foo === now);                // false
// 内层对象深拷贝成功
console.log(foo.title === now.title);    // false
// 内层函数深拷贝成功
console.log(foo.title.show === now.title.show);     // false
// 内层数组深拷贝成功
console.log(foo.title.list === now.title.list);     // false
// 内层属性深拷贝成功
now.title.sTitle = "this is new small title";
console.log(foo.title.sTitle);           // this is small title
console.log(now.title.sTitle);           // this is new small title

2. 利用js中对json的解析方法

let foo = {
    title:{
        sTitle:0,
        list:[1,2,{a:3,b:4}]
    }
}

let now = JSON.parse(JSON.stringify(foo));

console.log(foo);            // {title: {sTitle: 0, list: [1, 2, {a: 3, b: 4}]}}
console.log(now);            // {title: {sTitle: 0, list: [1, 2, {a: 3, b: 4}]}}
// 深拷贝成功
console.log(foo === now);    // false

可以拷贝多层对象,但受json数据的限制,无法拷贝函数,undefined,NaN属性

let foo = {
    title:{
        show:function(){},
        num:NaN,
        empty:undefined
    }
}

let now = JSON.parse(JSON.stringify(foo));

// 虽然实现了深拷贝,但show和empty属性丢失,num属性被转成了null
console.log(foo);            // {title: {{num: NaN, empty: undefined, show: ƒ}}}
console.log(now);            // {title: {num: null}}
console.log(foo === now);    // false

3. 利用ES6 提供的 Object.assign()

let foo = {
    title:{
        show:function(){},
        num:NaN,
        empty:undefined
    }
}

let now = {};

Object.assign(now, foo);

console.log(foo);   // {title: {{num: NaN, empty: undefined, show: ƒ}}}
console.log(now);   // {title: {{num: NaN, empty: undefined, show: ƒ}}}

// 外层对象深拷贝成功
console.log(foo === now);                // false
// 内层对象依然是浅拷贝
console.log(foo.title === now.title);    // true 

只能可以拷贝一层数据,无法拷贝多层数据

4. 利用ES6 提供的展开运算符:...

let foo = {
    title:{
        show:function(){},
        num:NaN,
        empty:undefined
    }
}

let now = {...foo};

console.log(foo);   // {title: {{num: NaN, empty: undefined, show: ƒ}}}
console.log(now);   // {title: {{num: NaN, empty: undefined, show: ƒ}}}

// 外层对象深拷贝成功
console.log(foo === now);                // false
// 内层对象依然是浅拷贝
console.log(foo.title === now.title);    // true 

5. 数组拷贝concat 方法

语法:arr1.concat(arr2)
concat() 方法用于连接两个或多个数组。

返回值:返回一个新的数组。该数组是通过把所有 arrayX 参数添加到 arrayObject 中生成的。如果要进行 concat() 操作的参数是数组,那么添加的是数组中的元素,而不是数组。
该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。

当数组的元素为一维时是深拷贝
一维以上是赋值引用

6. 数组拷贝slice方法

语法:arr.slice(start,end)
slice() 方法可从已有的数组中返回选定的元素。

返回值:返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。

  1. 没有参数是拷贝数组
  2. 只有一个参数是从该位置起到结束拷贝数组元素
  3. 两个参数,拷贝从起始位置到结束位置的元素(含头不含尾)
    当数组中的元素是一维时是深拷贝
    数组中元素是一维以上时是赋值引用

7.使用函数库lodash中的cloneDeep()方法

  1. 下载模块
cnpm i lodash --save 或 yarn add lodash
  1. 导入模块
import _ from 'lodash'
  1. 使用cloneDeep()方法对数据进行深拷贝
loodash.cloneDeep(obj) 
//深拷贝

8. 使用immutable-js

【简单学习】https://www.jianshu.com/p/2ae0507ed86d
【常见API简介】https://segmentfault.com/a/1190000010676878

总结

1. 数组有slice方法,concat方法等,也可以实现深拷贝,但只能对数组进行操作,不能操作对象;
2. 直接使用赋值运算符复制对象,只拷贝了地址,没有拷贝值,是浅拷贝;
3. 直接遍历对象,使用赋值运算符拷贝属性,只能拷贝单层数据,无法拷贝多层数据;
4. json数据和对象的解析,可以拷贝多层数据,但是如果对象中存在function,undefined,NaN,就会发生数据丢失;
5. js自身提供的诸多方法,如:Object.assign,展开运算符...,数组的slice,数组的concat等,只能拷贝单层数据,无法拷贝多层数据;
6. 使用递归思想,封装函数,借助赋值运算符,才是真正的深拷贝。最终解决方案:递归!
7.使用函数库中lodash的cloneDeep()方法
8.使用immutable-js


今天的分享就到这里了!谢谢大家!我要去睡觉了~~~

你可能感兴趣的:(JS深浅拷贝)