preface

First, let’s be clear about the goals of deep copy.

Deep copy not only copies the properties of the original object one by one, but also copies the objects contained in the properties of the original object to the new object recursively.

On this basis, we add two requirements

  1. Copy only enumerable properties
  2. Do not copy properties of stereotype objects on the stereotype chain

Of course, the goals may vary based on different business needs, which will not be considered in this article.

Deep Copy implementation (simplest version)

First, implement the simplest recursive deep copy version

function deepClone(obj) {
    // Initialize the object to be copied
    const copyObj = Array.isArray(obj)? [] : {};// The for in loop iterates through the properties of the object
    for (const key in obj){
        For in iterates over all enumerable properties of an object other than Symbol, including those on the prototype chain
        // If you only want to consider the properties of the object itself, you need to use hasOwnProperty
        if (obj.hasOwnProperty(key)) {
            Obj [key] is recursive if it is an object, and copies the basic type directly if it is not an object
            copyObj[key] = typeof obj[key] === 'object'? deepClone(obj[key]) : obj[key]; }}return copyObj;
}
// Test
var obj = {
    a:1.b:{}
};
var copy = deepClone(obj)
console.log(copy === obj);// false
copy.a = 2;
console.log(copy.a === obj.a);// false
console.log(copy.b === obj.b);// false
Copy the code

Implementation of deep Copy (circular Reference Version)

In some cases, objects may have circular references, and deep copies may recurse indefinitely and eventually overflow the stack. So in the case of circular references we need to do something.

// Map is used to store objects that have been copied. The address of the object to be copied is used as the key and the address of the object to be copied is used as the value
function deepClone(obj,copyMap = new Map(a)) {
    // Check whether the current object has been copied. If yes, return the copied object
    if(copyMap.has(obj)) return copyMap.get(obj);
    const copyObj = Array.isArray(obj)? [] : {};// Add the object that has been copied
    copyMap.set(obj,copyObj);
    for (const key in obj){
        if (obj.hasOwnProperty(key)) {
            copyObj[key] = typeof obj[key] === 'object'? deepClone(obj[key],copyMap) : obj[key]; }}return copyObj;
}
// Test that obj is an object that references itself indefinitely through the self property
var obj = {};
obj.self = obj;
var copy = deepClone(obj)
console.log(copy === obj);// false
console.log(copy.self === copy);// true
console.log(copy.self.self === copy);// true
Copy the code

We use maps to store the copied objects, but this is not the only solution. We can also use our own data structure to implement this.

Implementation of deep Copy (Symbol version)

As mentioned earlier, hasOwnProperty() cannot enumerate a property whose name is of type Symbol. If we want to copy a property whose name is of type Symbol, we need to use another function to do so.

Tips: Object property names can only bestringandSymboltype

. Let’s talk about the Object getOwnPropertySymbols () this method, it returns an array of a given all Symbol attribute of the Object itself (not Symbol attribute names not included in the array)

In order to reduce the amount of code, we are based on the simple version of the transformation

function deepClone(obj) {
    const copyObj = Array.isArray(obj)? [] : {};// Iterates over enumerable attributes that are not Symbol
    for (const key in obj){
        if (obj.hasOwnProperty(key)) {
            copyObj[key] = typeof obj[key] === 'object'? deepClone(obj[key]) : obj[key]; }}// Iterate over the Symbol attribute
    for (const key of Object.getOwnPropertySymbols(obj)){
        // To be more precise, let's remove non-enumerable attributes
        if (obj.propertyIsEnumerable(key)) {
            copyObj[key] = typeof obj[key] === 'object'? deepClone(obj[key]) : obj[key]; }}return copyObj;
}
// Test
var symbolKeyA = Symbol(a);//symbolKeyA cannot be enumerated
var symbolKeyB = Symbol(a);/ / symbolKeyB can be enumerated
var obj = Object.create({}, {
  [symbolKeyA]: {
    value: {},
    enumerable: false}}); obj[symbolKeyB] = {};var copy = deepClone(obj);
console.log(copy[symbolKeyA]); // undefined
console.log(copy[symbolKeyB]); // Object { }
console.log(copy[symbolKeyB] === obj[symbolKey]);// false
Copy the code

Conclusion that adds

  • In general, to traverse the attributes of the specified by the Object of all their property name (including not enumerated attribute but does not include Symbol value as the name of the attribute) with the Object. The getOwnPropertyNames ()

  • More specifically, if you simply get the enumerable attribute (not including the Symbol value as the name of the attribute) object.keys or use for… In loop, but for… The in loop also gets enumerable properties on the prototype chain, but you can filter them out with the hasOwnProperty() method.

  • . In particular, the Object getOwnPropertySymbols () method returns a given Object itself all the attributes of the Symbol of array (not Symbol attribute names not included in the array). Note that this includes non-enumerable properties but not properties on the prototype chain, which can be filtered out using the propertyIsEnumerable() method.

  • The easily overlooked static reflect.ownkeys () method returns an array of the target object’s own property keys. Equivalent to the Object. GetOwnPropertyNames () plus Object. GetOwnPropertySymbols ().

The example code is as follows:

  var symbolKeyA = Symbol('Enumerable Symbol');
  var symbolKeyB = Symbol('Unenumerable Symbol');
  var symbolKeyC = Symbol('Symbol on prototype object');
  var obj = Object.create({}, {
    [symbolKeyA]: {
      value: {},
      enumerable: false},'Non-enumerable properties'] : {value: {},
      enumerable: false}}); obj[symbolKeyB] = {}; obj['Instance Properties'] = {};
  obj.__proto__['Properties on prototype objects'] = {}; obj.__proto__[symbolKeyC]={};console.log(Reflect.ownKeys(obj));
  //[" non-enumerable attribute ", "instance attribute ", Symbol(Symbol))
  
  console.log(Object.getOwnPropertyNames(obj));
  // [" non-enumerable attribute ", "instance attribute "]
  
  console.log(Object.keys(obj));
  // [" instance properties "]
  
  for(const key in obj) {
      console.log(key);
      // "instance properties"
      // "Attribute on stereotype object"
  }
  
  console.log(Object.getOwnPropertySymbols(obj));
  // [Symbol(Symbol), Symbol(Symbol)]
  
Copy the code

Note: None of the above methods can access the Symbol attribute on the prototype object

The dizzy? Directly above!

The text after homework

Please figure out how to recreate the above example with reflect.ownkeys () and post the results in the comments section! Reflect can also be used with Proxy.

About me

Like to chat, like to share, like the cutting-edge 22 side dish chicken, new to the hope to get your attention. Internships/college recruiting opportunities are even better! Personal public account: Rat son’s front end CodeLife