thebjorn
thebjorn

Reputation: 27311

How to shallow copy a Javascript object while preserving getters/setters?

Background (why Object.update() or jQuery.extend() is not a solution, and why this is not a duplicate of the other how-to-copy-a-javascript-object questions): This version of copy, using a for-loop

function copy_obj(obj) {
    var copy = {};
    for (var attr in obj) if (obj.hasOwnProperty(attr)) {
        copy[attr] = obj[attr];
    }
    return copy;
}

only copies the values of the attributes (as does Object.update()), and fails on an object like:

var myobj = {
    _fname: 'fname',
    _lname: 'lname',
    get fullname() { return this._fname + ' ' + this._lname; },
    set fullname(v) { this._fname = v; }
};

i.e.

myobj.fullname === 'fname lname'  // true
myobj.fullname = 'Anton'
myobj.fullname === 'Anton lname'  // true

while

var mycopy = copy_obj(myobj);
mycopy.fullname === 'fname lname'  // true
mycopy.fullname = 'Anton'
mycopy.fullname === 'Anton'  // oops!

Question: I've figured out that I need to use getOwnPropertyNames/Descriptor to get/set the descriptors, but I haven't found any simple way to determine if something is just a plain value that can be copied directly -- what goes into the if-statement:

function copy_obj2(obj) {
    var mycopy = {};
    Object.getOwnPropertyNames(obj).forEach(function (prop) {
        var descriptor = Object.getOwnPropertyDescriptor(obj, prop);
        if (/* descriptor is a plain value */) {
            mycopy[prop] = obj[prop];
        } else {
            Object.defineProperty(mycopy, prop, descriptor);
        }
    });
    return mycopy;
}

(I'm only interested in copying object-like values, not Arrays or other basic types).

Upvotes: 6

Views: 1494

Answers (1)

user663031
user663031

Reputation:

You don't need to worry about the type of descriptor; whether it's a data-type descriptor or an accessor-type descriptor, you can just use it as is to create the property on the target object.

function copy_obj2(obj) {
    var mycopy = {};
    Object.getOwnPropertyNames(obj).forEach(function (prop) {
        var descriptor = Object.getOwnPropertyDescriptor(obj, prop);
        Object.defineProperty(mycopy, prop, descriptor);
    });
    return mycopy;
}

But you could also use getOwnPropertyDescriptors, with defineProperties:

function copy_obj2(obj) {
  return Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
}

If you also want to include the prototype in the clone, then:

function copy_obj2(obj) {
  return Object.create(Object.getPrototypeOf(obj),  Object.getOwnPropertyDescriptors(obj));
}

However, getOwnPropertyDescriptors is not available in IE. A polyfill is suggested here.

Upvotes: 3

Related Questions