Object Copy of Javascript

Posted by newcastle_unite on Sat, 18 May 2019 19:01:57 +0200

Translator: Crazy Technology House
Original text: https://smalldata.tech/blog/2...

Wechat Public Number: Front-end Pioneer
Welcome to pay attention and push you fresh front-end technology articles every day.

Before I begin, I will popularize some basic knowledge. Javascript objects are just pointers to a location in memory. These pointers are variable, that is, they can be reassigned. So just copying this pointer results in two pointers pointing to the same address in memory.

var foo = {
    a : "abc"
}
console.log(foo.a);
// abc

var bar = foo;
console.log(bar.a);
// abc

foo.a = "yo foo";
console.log(foo.a);
// yo foo
console.log(bar.a);
// yo foo

bar.a = "whatup bar?";
console.log(foo.a);
// whatup bar?
console.log(bar.a);
// whatup bar?    

As you can see from the example above, both foo and bar can change with each other. So when copying the object in Javascript, we should consider it according to the actual situation.

shallow copy

If all the attributes of the object to be operated on are value types, you can use extended grammar or Object.assign(...)

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

You can see that both methods can copy attributes from multiple different source objects into one target object.

var obj1 = { foo: "foo" };
var obj2 = { bar: "bar" };
var copySpread = { ...obj1, ...obj2 };
// Object { foo: "foo", bar: "bar" }
var copyAssign = Object.assign({}, obj1, obj2);
// Object { foo: "foo", bar: "bar" }

The above method is problematic. If the object's attributes are also objects, only those pointers are actually copied, which is the same effect as var bar = foo; as in the first code.

var foo = { a: 0 , b: { c: 0 } };
var copy = { ...foo };
copy.a = 1;
copy.b.c = 2;
console.dir(foo);
// { a: 0, b: { c: 2 } }
console.dir(copy);
// { a: 1, b: { c: 2 } }

Deep copy (limited)

One possible way to make a deep copy of an object is to serialize the object into a string and then deserialize it.

var obj = { a: 0, b: { c: 0 } };
var copy = JSON.parse(JSON.stringify(obj));

Unfortunately, this method only works if the object contains serializable values without circular references. Common things that can't be serialized are date objects -- although they display a stringed ISO date format, JSON.parse only parses it into a string, not a date type.

Deep copy (less restrictive)

For some more complex scenarios, we can use a name provided by HTML5 Structural Cloning New algorithm. However, as of the publication of this article, some built-in types are still not supported, but compared with JSON.parse, it supports more types: Date, RegExp, Map, Set, Blob, FileList, ImageData, sparse and typed Array. It also maintains references to cloned objects, which enables it to support copies of circular reference structures that are not supported in the previously mentioned serialization.

There is no direct call to structured cloning yet, but some new browser features use this algorithm at the bottom. So deep-copy objects may depend on a series of environments.

Via Message Channels: The principle is to borrow the serialization algorithm used in communication. Because it is event-based, cloning here is also an asynchronous operation.

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Via the history API: history.pushState() and history.replaceState() will make a structured clone of their first parameter! It should be noted that this method is synchronous, because the operation speed of browser history is not very fast, if this method is frequently called, it will lead to browser card death.

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

Via notification API When a notification instance is created, the constructor makes a structured clone of its related data. It should be noted that it will try to display browser notifications to users, but it will not do anything unless it receives a request for user permission to display notifications. Once the user clicks on the consent, notification will be closed immediately.

const structuredClone = obj => {
  const n = new Notification("", {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

Deep copy with Node.js

Version 8.0.0 of Node.js provides one Serialized api It is comparable to structured cloning. However, at the time of publication of this article, this API was only marked as experimental:

const v8 = require('v8');
const buf = v8.serialize({a: 'foo', b: new Date()});
const cloned = v8.deserialize(buf);
cloned.b.getMonth();

The cloneDeep function of lodash can be considered as a stable method under version 8.0.0, and its idea is more or less based on structured cloning algorithm.

conclusion

The best algorithm for object copy in Javascript depends largely on the environment in which it is used and the type of object you need to copy. Although lodash is the safest generic deep copy function, if you encapsulate it yourself, you may be able to achieve a more efficient implementation. Here is a simple deep copy, which is also applicable to Date date objects:

function deepClone(obj) {
  var copy;

  // Handle the 3 simple types, and null or undefined
  if (null == obj || "object" != typeof obj) return obj;

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

  // Handle Array
  if (obj instanceof Array) {
    copy = [];
    for (var i = 0, len = obj.length; i < len; i++) {
        copy[i] = deepClone(obj[i]);
    }
    return copy;
  }

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

  // Handle Object
  if (obj instanceof Object) {
      copy = {};
      for (var attr in obj) {
          if (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]);
      }
      return copy;
  }

  throw new Error("Unable to copy obj as type isn't supported " + obj.constructor.name);
}

I'm looking forward to the day when structured cloning can be used casually so that object copying is no longer a headache.^^

Wechat Public Number: Front-end Pioneer

Welcome to scan the two-dimensional code, pay attention to the public number, and push you fresh front-end technical articles every day.

Welcome to continue reading other highly praised articles in this column:

Topics: Javascript JSON less html5