Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

How JavaScript implements deep copy


Jun 01, 2021 Article blog


Table of contents


In JavaScript development, we often encounter situations where deep copying is required, and we often ask this question in interviews, so what is a shallow copy and what is a deep copy?

What is a shallow copy

With regard to the concept of shallow copying, I saw a statement on the Internet that goes directly to the code.

var person = {name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}};
var person1 = person;       //他们认为这是浅拷贝

But I personally think that this one above doesn't involve a copy at all, it's just a simple reference assignment. In my understanding, a shallow copy should be a property that does not take into account the reference type of the object, and only copies all members of the current object, as follows:

function copy(obj){
    var objCopy = {};
    for(var key in obj){
        objCopy[key] = obj[key];
    }
    return objCopy;
}


var person = {name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}};
var personCopy = copy(person);

In this code above, the person object has two basic types of properties name and age a reference type property car and when copied using methods such as the one above, name and age properties are copied normally, but car property only makes copies of references, which causes the copied personCopy and person to share a car object. This is called a shallow copy.

What is a deep copy

Deep copy is when copying, you need to copy the properties of all reference types within the object you are currently copying, which means that no data is shared between the copied object and the original object, and everything is exclusive to itself.

How to implement deep copy

There are several factors to consider when implementing a deep copy:

  • Whether the incoming object is an object created using the literal amount of the object or an object generated by the constructor

  • If the object was created by a constructor, do you want to copy the properties on the prototype chain?

  • If you want to copy properties on the prototype chain, which one is retained if there are multiple properties with the same name on the prototype chain

  • Handles the problem of circular references

Third-party libraries implement deep copies

jQuery's $.extend()

We can do deep replication through $.extend() method. T hankfully, we can recursive extend by adding a parameter in jQuery C all $.extend(true, {}, ...) Deep replication is possible, refer to the following example:

var x = {
    a: 1,
    b: { f: { g: 1 } },
    c: [ 1, 2, 3 ]
};


var y = $.extend({}, x),          //shallow copy
    z = $.extend(true, {}, x);    //deep copy


y.b.f === x.b.f       // true
z.b.f === x.b.f       // false

But jQuery $.extend() approach has drawbacks, and what are the drawbacks? Let's look at the following example:

var objA = {};
var objB = {};


objA.b = objB;
objB.a = objA;


$.extend(true,{},a);


//这个时候就出现异常了
//Uncaught RangeError: Maximum call stack size exceeded(…)

That is, $.extend() in jQuery does not address the problem of circular references.

Use JSON objects for deep copy

Using the parse and stringify methods of JSON global objects for deep replication is also a simple and tricky approach.

function jsonClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
var clone = jsonClone({ a:1 });

However, there are hidden pits that can be used in this way, and the only objects it can handle correctly are Number String Boolean Array flat objects, which are data structures that can be represented directly by json

Make your own wheels

Let's give you a simple solution that, of course, is implemented by reference to someone else's way. I hope it will be useful to all of you.

var clone = (function() {
    //这个方法用来获取对象的类型 返回值为字符串类型 "Object RegExp Date Array..."
    var classof = function(o) {
        if (o === null) {
            return "null";
        }
        if (o === undefined) {
            return "undefined";
        }
        // 这里的Object.prototype.toString很可能用的就是Object.prototype.constructor.name
        // 这里使用Object.prototype.toString来生成类型字符串
        var className = Object.prototype.toString.call(o).slice(8, -1);
        return className;
    };


    //这里这个变量我们用来存储已经保存过的属性,目的在于处理循环引用的问题
    var references = null;


    //遇到不同类型的对象的处理方式
    var handlers = {
        //正则表达式的处理
        'RegExp': function(reg) {
            var flags = '';
            flags += reg.global ? 'g' : '';
            flags += reg.multiline ? 'm' : '';
            flags += reg.ignoreCase ? 'i' : '';
            return new RegExp(reg.source, flags);
        },
        //时间对象处理
        'Date': function(date) {
            return new Date(+date);
        },
        //数组处理 第二个参数为是否做浅拷贝
        'Array': function(arr, shallow) {
            var newArr = [],
            i;
            for (i = 0; i < arr.length; i++) {
                if (shallow) {
                    newArr[i] = arr[i];
                } else {
                    //这里我们通过reference数组来处理循环引用问题
                    if (references.indexOf(arr[i]) !== -1) {
                        continue;
                    }
                    var handler = handlers[classof(arr[i])];
                    if (handler) {
                        references.push(arr[i]);
                        newArr[i] = handler(arr[i], false);
                    } else {
                        newArr[i] = arr[i];
                    }
                }
            }
            return newArr;
        },
        //正常对象的处理 第二个参数为是否做浅拷贝
        'Object': function(obj, shallow) {
            var newObj = {}, prop, handler;
            for (prop in obj) {
                //关于原型中属性的处理太过复杂,我们这里暂时不做处理
                //所以只对对象本身的属性做拷贝
                if (obj.hasOwnProperty(prop)) {
                    if (shallow) {
                        newObj[prop] = obj[prop];
                    } else {
                        //这里还是处理循环引用的问题
                        if (references.indexOf(obj[prop]) !== -1) {
                            continue;
                        }


                        handler = handlers[classof(obj[prop])];
                        //如果没有对应的处理方式,那么就直接复制
                        if (handler) {
                            references.push(obj[prop]);
                            newObj[prop] = handler(obj[prop], false);
                        } else {
                            newObj[prop] = obj[prop];
                        }
                    }
                }
            }
            return newObj;
        }
    };


    return function(obj, shallow) {
        //首先重置我们用来处理循环引用的这个变量
        references = [];
        //我们默认处理为浅拷贝
        shallow = shallow === undefined ? true : false;
        var handler = handlers[classof(obj)];
        return handler ? handler(obj, shallow) : obj;
    };
}());


(function() {
    //下面是一些测试代码
    var date = new Date();
    var reg = /hello word/gi;
    var obj = {
        prop: 'this ia a string',
        arr: [1, 2, 3],
        o: {
            wow: 'aha'
        }
    };
    var refer1 = {
        arr: [1, 2, 3]
    };
    var refer2 = {
        refer: refer1
    };
    refer1.refer = refer2;


    var cloneDate = clone(date, false);
    var cloneReg = clone(reg, false);
    var cloneObj = clone(obj, false);
    alert((date !== cloneDate) && (date.valueOf() === cloneDate.valueOf()));
    alert((cloneReg !== reg) && (reg.toString() === cloneReg.toString()));
    alert((obj !== cloneObj) && (obj.arr !== cloneObj.arr) && (obj.o !== cloneObj.o) && (JSON.stringify(obj) === JSON.stringify(cloneObj)));


    clone(refer2, false);
    alert("I'm not dead yet!");
    // Output:
    // true
    // true
    // true
    // I'm not dead yet!
}());

The above is about JavaScript copy of some knowledge, I hope to help you, interested in JavaScript students can look at the tutorial

JavaScript tutorial: https://www.w3cschool.cn/javascript/

JavaScript Microsyscope: https://www.w3cschool.cn/minicourse/play/jscourse