Implementation of shallow copy and deep copy in javascript

Excerpted from the GitHub URL: https://github.com/wengjq/Blog/issues/3

original:

1. The variable type of JavaScript

(1) Basic types:
5 basic data types Undefined, Null, Boolean, Number and String. Variables are directly stored by value, and simple data segments stored in stack memory can be accessed directly.

(2) Reference type:
The object stored in the heap memory, the variable holds a pointer, and this pointer points to another location. When you need to access the value of a reference type (such as an object, an array, etc.), the address pointer of the object is first obtained from the stack, and then the required data is obtained from the heap memory.

JavaScript storage objects are all addresses, so a shallow copy will cause obj1 and obj2 to point to the same memory address. When the content of one of the parties is changed, the modification on the original memory will cause both the copied object and the source object to change, while the deep copy is to open up a new memory address and copy the attributes of the original object one by one. The respective operations on the copied object and the source object do not affect each other.

For example: array copy

//浅拷贝,双向改变,指向同一片内存空间
var arr1 = [1, 2, 3];
var arr2 = arr1;
arr1[0] = 'change';
console.log('shallow copy: ' + arr1 + " );   //shallow copy: change,2,3
console.log('shallow copy: ' + arr2 + " );   //shallow copy: change,2,3

2. Implementation of shallow copy

2.1, simple reference copy###

function shallowClone(copyObj) {
  var obj = {};
  for ( var i in copyObj) {
    obj[i] = copyObj[i];
  }
  return obj;
}
var x = {
  a: 1,
  b: { f: { g: 1 } },
  c: [ 1, 2, 3 ]
};
var y = shallowClone(x);
console.log(y.b.f === x.b.f);     // true

2.2、Object.assign()

The Object.assign() method can copy any number of enumerable properties of the source object itself to the target object, and then return the target object.

var x = {
  a: 1,
  b: { f: { g: 1 } },
  c: [ 1, 2, 3 ]
};
var y = Object.assign({}, x);
console.log(y.b.f === x.b.f);     // true

3. Implementation of deep copy

3.1, Array's slice and concat methods

The slice and concat methods of Array do not modify the original array, but only return a new array with a shallow copy of the elements in the original array. It's put in a deep copy because it looks like a deep copy. And actually it's a shallow copy. The elements of the original array are copied according to the following rules:

  • If the element is an object reference (not an actual object), slice will copy the object reference into the new array. Both object references refer to the same object. If the referenced object changes, this element in the new and original arrays also changes.
  • For strings, numbers, and Boolean values ​​(not String, Number, or Boolean objects), slice copies the values ​​into a new array. Modifying these strings or numbers or booleans in another array will not affect the other array.

If a new element is added to either of the two arrays, the other will not be affected. Examples are as follows:

var array = [1,2,3]; 
var array_shallow = array; 
var array_concat = array.concat(); 
var array_slice = array.slice(0); 
console.log(array === array_shallow); //true 
console.log(array === array_slice); //false,“看起来”像深拷贝
console.log(array === array_concat); //false,“看起来”像深拷贝

It can be seen that concat and slice return different array instances, which are different from direct reference copying. From another example, we can see that the concat and slice of Array are not really deep copies, and the object elements (Object, Array, etc.) in the array are only copied by reference. as follows:

var array = [1, [1,2,3], {name:"array"}]; 
var array_concat = array.concat();
var array_slice = array.slice(0);
array_concat[1][0] = 5;  //改变array_concat中数组元素的值 
console.log(array[1]); //[5,2,3] 
console.log(array_slice[1]); //[5,2,3] 
array_slice[2].name = "array_slice"; //改变array_slice中对象元素的值 
console.log(array[2].name); //array_slice
console.log(array_concat[2].name); //array_slice

3.2, parse and stringify of JSON objects

JSON object is a new type introduced in ES5 (supported browsers are IE8+). The JSON object parse method can deserialize JSON strings into JS objects, and the stringify method can serialize JS objects into JSON strings. With these two method, can also implement a deep copy of the object.

//例1
var source = { name:"source", child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
target.name = "target";  //改变target的name属性
console.log(source.name); //source 
console.log(target.name); //target
target.child.name = "target child"; //改变target的child 
console.log(source.child.name); //child 
console.log(target.child.name); //target child
//例2
var source = { name:function(){console.log(1);}, child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
//例3
var source = { name:function(){console.log(1);}, child:new RegExp("e") }
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined
console.log(target.child); //Object {}

This method is relatively simple to use, can meet basic deep copy requirements, and can handle all data types that can be represented in JSON format, but cannot perform deep copy for regular expression types, function types, etc. (and will directly lose the corresponding value) . Another downside is that it throws away the object's constructor. That is, after a deep copy, no matter what the original constructor of the object is, it will become an Object after the deep copy. At the same time, if there is a circular reference in the object, it cannot be handled correctly.

4. jQuery.extend() method source code implementation

The source code of jQuery - src/core.js #L121 The source code and analysis are as follows:

jQuery.extend = jQuery.fn.extend = function() { //给jQuery对象和jQuery原型对象都添加了extend扩展方法
  var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
  i = 1,
  length = arguments.length,
  deep = false;
  //以上其中的变量:options是一个缓存变量,用来缓存arguments[i],name是用来接收将要被扩展对象的key,src改变之前target对象上每个key对应的value。
  //copy传入对象上每个key对应的value,copyIsArray判定copy是否为一个数组,clone深拷贝中用来临时存对象或数组的src。

  // 处理深拷贝的情况
  if (typeof target === "boolean") {
    deep = target;
    target = arguments[1] || {};
    //跳过布尔值和目标 
    i++;
  }

  // 控制当target不是object或者function的情况
  if (typeof target !== "object" && !jQuery.isFunction(target)) {
    target = {};
  }

  // 当参数列表长度等于i的时候,扩展jQuery对象自身。
  if (length === i) {
    target = this; --i;
  }
  for (; i < length; i++) {
    if ((options = arguments[i]) != null) {
      // 扩展基础对象
      for (name in options) {
        src = target[name];	
        copy = options[name];

        // 防止永无止境的循环,这里举个例子,
            // 如 var a = {name : b};
            // var b = {name : a}
            // var c = $.extend(a, b);
            // console.log(c);
            // 如果没有这个判断变成可以无限展开的对象
            // 加上这句判断结果是 {name: undefined}
        if (target === copy) {
          continue;
        }
        if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
          if (copyIsArray) {
            copyIsArray = false;
            clone = src && jQuery.isArray(src) ? src: []; // 如果src存在且是数组的话就让clone副本等于src否则等于空数组。
          } else {
            clone = src && jQuery.isPlainObject(src) ? src: {}; // 如果src存在且是对象的话就让clone副本等于src否则等于空数组。
          }
          // 递归拷贝
          target[name] = jQuery.extend(deep, clone, copy);
        } else if (copy !== undefined) {
          target[name] = copy; // 若原对象存在name属性,则直接覆盖掉;若不存在,则创建新的属性。
        }
      }
    }
  }
  // 返回修改的对象
  return target;
};

jQuery's extend method uses basic recursive ideas to implement shallow and deep copies, but this method cannot handle circular references inside the source object, for example:

var a = {"name":"aaa"};
var b = {"name":"bbb"};
a.child = b;
b.parent = a;
$.extend(true,{},a);//直接报了栈溢出。Uncaught RangeError: Maximum call stack size exceeded

5. Implement a copy method yourself

(function ($) {
    'use strict';

    var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' ');

	function type () {
	   return Object.prototype.toString.call(this).slice(8, -1);
	}

	for (var i = types.length; i--;) {
	    $['is' + types[i]] = (function (self) {
	        return function (elem) {
	           return type.call(elem) === self;
	        };
	    })(types[i]);
	}

    return $;
})(window.$ || (window.$ = {}));//类型判断

function copy (obj,deep) { 
    if (obj === null || (typeof obj !== "object" && !$.isFunction(obj))) { 
        return obj; 
    } 

    if ($.isFunction(obj)) {
    	return new Function("return " + obj.toString())();
    }
    else {
        var name, target = $.isArray(obj) ? [] : {}, value; 

        for (name in obj) { 
            value = obj[name]; 

            if (value === obj) {
            	continue;
            }

            if (deep) {
                if ($.isArray(value) || $.isObject(value)) {
                    target[name] = copy(value,deep);
                } else if ($.isFunction(value)) {
                    target[name] = new Function("return " + value.toString())();
                } else {
            	    target[name] = value;
                } 
            } else {
            	target[name] = value;
            } 
        } 
        return target;
    }         
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325361538&siteId=291194637
Recommended