资讯专栏INFORMATION COLUMN

浅谈JavaScript的浅拷贝与深拷贝

娣辩孩 / 3141人阅读

摘要:引用数据类型是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。栈和堆的区别其实浅拷贝和深拷贝的主要区别就是数据在内存中的存储类型不同。这里,对存在子对象的对象进行拷贝的时候,就是深拷贝了。

数据类型

在开始拷贝之前,我们从JavaScript的数据类型和内存存放地址讲起。
数据类型分为基本数据类型引用数据类型

基本数据类型主要包括undefined,boolean,number, string,null。

基本数据类型主要存放在栈(stack),存放在栈中的数据简单,大小确定。存放在栈内存中的数据是直接按值存放的,是可以直接访问的。

基本数据类型的比较是值的比较,只要它们的值相等就认为它们是相等的。

let a = 1;
let b = 1;
console.log(a === b); //true

这里使用严格相等,主要是为了==会进行类型转换。

let a = true;
let b = 1;
console.log(a == b); //true

引用数据类型也就是对象类型Object type,比如object,array, function,date等。
引用数据类型是存放在堆(heap)内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。

引用数据类型的比较是引用的比较
所以我们每次对js中的引用类型进行操作的时候,都是操作其保存在栈内存中的指针,所以比较两个引用数据类型,是看它们的指针是否指向同一个对象。

let foo = {a: 1, b: 2};
let bar = {a: 1, b: 2};
console.log(foo === bar); //false

虽然变量foo和变量bar所表示的内容是一样的,但是其在内存中的位置不一样,也就是变量foo和bar在栈内存中存放的指针指向的不是堆内存中的同一个对象,所以它们是不相等的。

栈和堆的区别

其实浅拷贝和深拷贝的主要区别就是数据在内存中的存储类型不同。
栈和堆都是内存中划分出来用来存储的区域。
栈(stack) 是自动分配的内存空间,由系统自动释放;
堆(heap) 则是动态分配的内存,大小不定也不会自动释放。

浅拷贝

如果你的对象只有值类型的属性,可以用ES6的新语法Object.assign(...)实现拷贝

//浅拷贝
let obj = {foo: "foo", bar: "bar"};

let shallowCopy = { ...obj };  //{foo: "foo", bar: "bar"}
//浅拷贝
let obj = {foo: "foo", bar: "bar"};

let shallowCopy = Object.assign({}, obj); //{foo: "foo", bar: "bar"}

我们接着来看下浅拷贝和赋值(=) 的区别

let obj = {foo: "foo", bar: "bar"};
let shallowCopy = { ...obj }; //{foo: "foo", bar: "bar"}
let obj2= obj; //{foo: "foo", bar: "bar"}
shallowCopy.foo = 1;
obj2.bar = 1
console.log(obj); //{foo: "foo", bar: 1};

可以看出赋值得到的obj2和最初的obj指向的是同一对象,改变数据会使原数据一同改变。
而浅拷贝得到的shallowCopy则将obj的第一层数据对象拷贝到了,和源数据不指向同一对象,改变不会使原数据一同改变。

深拷贝

但是Object.assign(...)方法只能进行对象的一层拷贝。对于对象的属性是对象的对象,他不能进行深层拷贝。
迷糊了吧?直接代码解释

let foo = {a: 0, b: {c: 0}};
let copy = { ...foo };
copy.a = 1;
copy.b.c = 1;
console.log(copy); //{a: 1, b: {c: 1}};
console.log(foo); //{a: 0, b: {c: 1}};

可以看到,使用Object.assign(...)方法拷贝的copy对象的二层对象发生改变的时候,依然会使原数据一同改变。
这里,对存在子对象的对象进行拷贝的时候,就是深拷贝了。

浅拷贝:将B对象拷贝到A对象中,不包括B里面的子对象
深拷贝:将B对象拷贝到A对象中,包括B里面的子对象

深拷贝实现的方法:

这里只说几种常用方法,
1.JSON.parse(JSON.stringify( ));

    let foo = {a: 0, b: {c: 0}};
       let copy = JSON.parse(JSON.stringify(foo));
       copy.a = 1;
       copy.b.c = 1;

       console.log(copy); //{a: 1, b: {c: 1}};
       console.log(foo); //{a: 0, b: {c: 0}};

2.递归拷贝

    function deepCopy(initialObj, finalObj){
            let obj = finalObj || {};
            for(let i in initialObj) {
                if(typeof initialObj[i] === "object") {
                    obj[i] = (initialObj[i].constructor === Array) ? [] : {};
                    arguments.callee(initialObj[i], obj[i]);
                }else{
                    obj[i] = initialObj[i];
                }
            }
            return obj;
        }


        var foo = {a: 0, b: {c: 0}};
        var str = {};

        deepCopy(foo, str);

        str.a = 1;
        str.b.c = 1;

        console.log(str); //{a: 1, b: {c: 1}};
        console.log(foo); //{a: 0, b: {c: 0}};

上述代码确实可以实现深拷贝,但是当遇到两个互相引用的对象,会出现死循环的情况。

为了避免相互引用的对象导致死循环的情况,则应该在遍历的时候判断是否相互引用对象,如果是则退出循环。

改进版代码如下:

        function deepCopy(initialObj, finalObj) {
            let obj = finalObj || {};
            for(let i in initialObj){
                let prop = initialObj[i];//避免相互引用导致死循环,如initialObj.a = initialObj的情况
                if(prop === obj) {
                    continue;
                }
                if(typeof prop === "object"){
                    obj[i] = (prop.constructor === Array) ? [] : {};
                    arguments.callee(prop, obj[i]);
                }else{
                    obj[i] = prop;
                }
            }
            return obj;
        }


        var foo = {a: 0, b: {c: 0}};
        var str = {};

        deepCopy(foo, str);

        str.a = 1;
        str.b.c = 1;

        console.log(str); //{a: 1, b: {c: 1}};
        console.log(foo); //{a: 0, b: {c: 0}};

3.使用Object.create( )方法

        function deepCopy(initialObj, finalObj){
            let obj = finalObj || {};
            for(let i in initialObj){
                let prop = initialObj[i];  //避免相互引用对象导致死循环,如initialObj[i].a = initialObj的情况
                if(prop === obj){
                    continue;
                }
                if(typeof prop === "object"){
                    obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
                }else{
                    obj[i] = prop;
                }
            }
            return obj;
        }


        let foo = {a: 0, b: {c: 0}};
        let str = {};

        deepCopy(foo, str);

        str.a = 1;
        str.b.c = 1;

        console.log(str); //{a: 1, b: {c: 1}};
        console.log(foo); //{a: 0, b: {c: 0}};

4.jQuery

jQuery提供了一个$.extend可以实现深拷贝

var $ = require("jquery);

let foo = {a: 0, b: {c: 0}};
let str = $.extend(true, {}, foo);
str.a = 1;
str.b.c = 1;

console.log(str); //{a: 1, b: {c: 1}};
console.log(foo); //{a: 0, b: {c: 0}};

5.lodash
另一个很热门的函数库lodash,也有提供_.cloneDeep用来深拷贝

var _ = require("lodash);

let foo = {a: 0, b: {c: 0}};
let str = _.cloneDeep(foo);
str.a = 1;
str.b.c = 1;

console.log(str); //{a: 1, b: {c: 1}};
console.log(foo); //{a: 0, b: {c: 0}};

局限性
所有深拷贝的方法并不适用于所有类型的对象。当然还有其他的坑,像是如何拷贝原型链上的属性?如何拷贝不可枚举属性等等。
虽然lodash是最安全的通用深拷贝方法,但如果你自己动手,可能会依据需求写出最适合你的更高效的深拷贝的方法:

//适用于日期的简单深拷贝的例子
        function deepCopy(obj) {
            let copy;

            //处理三种简单的引用数据类型加上undefined和null
            if(obj == null || typeof obj != "object") return obj;

            //处理Date
            if(obj instanceof Date){
                copy = new Date();
                copy.setTime(obj.getTime());
                return copy;
            }

            //处理Array
            if(obj instanceof Array) {
                copy = [];
                for(let i = 0; i < obj.length; i++) {
                    copy[i] = deepCopy(obj[i]);
                }
                return copy;
            }

            //处理Function
            if(obj instanceof Function) {
                copy = function() {
                    return obj.apply(this, arguments);
                }
                return copy;
            }

            //处理Object
            if(obj instanceof Object) {
                copy = {};
                for(let attr in obj) {
                    if(obj.hasOwnProperty(attr)) copy[attr] = deepCopy(obj[attr]);
                }
                return copy;
            }


            throw new Error("无法深拷贝" +obj.constructor+ "类型的数据")
        }

快乐拷贝

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99476.html

相关文章

  • 关于js的浅拷贝与深拷贝

    摘要:原文地址浅拷贝和深拷贝只针对像这样的复杂对象的简单来说,浅拷贝只拷贝一层对象的属性,而深拷贝则递归拷贝了所有层级。浅拷贝通过来实现浅拷贝。 原文地址:http://www.silenceboy.com/201... 浅拷贝和深拷贝只针对像Object, Array这样的复杂对象的.简单来说,浅拷贝只拷贝一层对象的属性,而深拷贝则递归拷贝了所有层级。 浅拷贝 通过 Object.ass...

    summerpxy 评论0 收藏0
  • JavaScript的浅拷贝与深拷贝

    摘要:所以,深拷贝是对对象以及对象的所有子对象进行拷贝实现方式就是递归调用浅拷贝对于深拷贝的对象,改变源对象不会对得到的对象有影响。 为什么会有浅拷贝与深拷贝什么是浅拷贝与深拷贝如何实现浅拷贝与深拷贝好了,问题出来了,那么下面就让我们带着这几个问题去探究一下吧! 如果文章中有出现纰漏、错误之处,还请看到的小伙伴多多指教,先行谢过 以下↓ 数据类型在开始了解 浅拷贝 与 深拷贝 之前,让我们先...

    546669204 评论0 收藏0
  • JavaScript的浅拷贝与深拷贝

    摘要:所以,深拷贝是对对象以及对象的所有子对象进行拷贝实现方式就是递归调用浅拷贝对于深拷贝的对象,改变源对象不会对得到的对象有影响。 上一篇 JavaScript中的继承 前言 文章开始之前,让我们先思考一下这几个问题: 为什么会有浅拷贝与深拷贝 什么是浅拷贝与深拷贝 如何实现浅拷贝与深拷贝 好了,问题出来了,那么下面就让我们带着这几个问题去探究一下吧! 如果文章中有出现纰漏、错误之处...

    AZmake 评论0 收藏0
  • 关于JavaScript的浅拷贝和深拷贝

    摘要:引用类型值引用类型值是保存在堆内存中的对象,变量保存的只是指向该内存的地址,在复制引用类型值的时候,其实只复制了指向该内存的地址。 前言 要理解 JavaScript中浅拷贝和深拷贝的区别,首先要明白JavaScript的数据类型。JavaScript有两种数据类型,基础数据类型和引用数据类型。js的基本类型:undefined,null,string,boolean,number,s...

    shenhualong 评论0 收藏0
  • JS引用类型数据的浅拷贝与深拷贝

    摘要:拷贝到,属性均顺利拷贝。大辉小辉,小辉,大辉小辉,小辉,大辉但是,若修改的属性变为对象或数组时,那么对象之间就会发生关联。深拷贝不希望对象之间产生关联,那么这时候可以用到深拷贝。 浅拷贝 之前文章提到,在定义一个对象或数组时,变量存放的往往只是一个地址。当我们对堆内存中的对象复制时,如果属性是对象或数组时,这时候我们拷贝的只是一个栈内存的指针。因此b对象在访问该属性时,会根据指针寻找...

    MangoGoing 评论0 收藏0

发表评论

0条评论

娣辩孩

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<