Copying object properties, the robust way

If, like me, you try to avoid using heavy libraries when not needed, you must have definitely written a helper to copy properties from one object to another at some point. It’s needed so often that it’s just silly to write the same loops over and over again.

These days, most of my time is spent working on my research project at MIT, which I will hopefully reveal later this year. In that, I’m using a lightweight homegrown helper library, which I might release separately at some point as I think it has potential in its own right, for a number of reasons.

Of course, it needed to have a simple extend() method as well, to copy properties from one object to another. Let’s assume for the purposes of this article that we’re talking about shallow copying, that overwrites are allowed, and let’s omit hasOwnProperty() checks to make code easier to read.

It’s a simple task, right? Our first attempt might look like this:

$.extend = function (to, from) {
	for (var property in from) {
		to[property] = from[property];
	}
	
	return to;
}

This works fine, until you try it on objects with accessors or other types of properties defined via Object.defineProperty() or get/set keywords. What do you do then? Our next iteration could look like this:

$.extend = function (to, from) {
	for (var property in from) {
		Object.defineProperty(to, property, Object.getOwnPropertyDescriptor(from, property));
	}
	
	return to;
}

This works much better, until it fails, and it can fail pretty epically. Try this:

$.extend(document.body.style, {
	backgroundColor: "red"
});

Both in Chrome and Firefox, the results are super weird. Even though reading document.body.style.backgroundColor will return "red", no style will have actually been applied. In Firefox it even destroyed the native setter entirely and any future attempts to set document.body.style.backgroundColor in the console did absolutely nothing.

In contrast, the previous naïve approach worked fine for this. It’s clear that we need to somehow combine the two approaches, using Object.defineProperty() only when actually needed. But when is it actually not needed?

One obvious case is if the descriptor is undefined (such as with some native properties). Also, in simple properties, such as those in our object literal, the descriptor will be of the form {value: somevalue, writable: true, enumerable: true, configurable: true}. So, the next obvious step would be:

$.extend = function (to, from) {
	var descriptor = Object.getOwnPropertyDescriptor(from, property);

	if (descriptor && (!descriptor.writable || !descriptor.configurable || !descriptor.enumerable || descriptor.get || descriptor.set)) {
		Object.defineProperty(to, property, descriptor);
	}
	else {
		to[property] = from[property];
	}
}

This works perfectly, but is a little clumsy. I’ve currently left it at that, but any suggestions for making it more elegant are welcome 🙂

FWIW, I looked at jQuery’s implementation of jQuery.extend() after this, and it seems it doesn’t even handle accessors at all, unless I missed something. Time for a pull request, perhaps…

Edit: As MaxArt pointed out in the comments, there is a similar native method in ES6, Object.assign(). However, it does not deal with copying accessors, so does not deal with this problem either.

  • MaxArt

    Nice one, Lea. Have you tried Object.assign, by the way? It should copy all enumerable properties and symbols, including their descriptors and all, and should work fine with .style properties.
    Or, at least, it works for me in Firefox. I don’t know if polyfills around do the trick.

    But I don’t recommend doing that pull request for jQuery: it would be a breaking change. jQuery users are used to have it like that.
    It might lead to an extended (heh) extend method with a different name, though.

    • Nice one, I somehow hadn’t even noticed this existed! Great to have a native method for this, though what a horrible name (assign? Makes no sense to me without the docs).

      Looking at the docs it seems that it doesn’t actually copy getters/setters though.

      • MaxArt

        Yeah, I know, sometimes we get weird names… *shrugs*
        That’s maybe why you have overlooked it.

      • WebReflection

        precisely, assign doesn’t work. And what you (me and everyone else) needs is `Object.getOwnPropertyDescriptors` which is here https://gist.github.com/WebReflection/9317065 and proposed in ES7 so that you can simply `Object.defineProperties(target, Object.getOwnPropertyDescriptors(source))` . Anyway, about your script, AFAIK there is no possible case a descriptor is null, that happens only if you loop the source object through a fir/in which might bring in inherited properties. You can simplify that via `Object.keys` or `Object.getOwnPropertyNames` (eventually plus `Object.getOwnpropertySymbols`)

        • Hi Andrea,
          My point was that copying descriptors doesn’t always work, see my style example. A property could indeed return a descriptor but Object.defineProperty not work with it. Again, see my .style example.
          Not sure which getters/setters returned null/undefined, but I believe i’ve seen it a few times with some native ones. Maybe I didn’t notice they were inherited? Entirely possible, and anything I tried now doesn’t seem to reproduce it. Hmmm.

        • WebReflection

          Lea I don’t see the example you mention … in one you are doing a for/in which exposes the undefined problem due inherited properties showing up with enumerables, in the other you test descriptor properties which is not needed if you loop over **own** property values because the descriptor is always available and by standard it works like that. Eventually the problem is that you are not trying to just copy/clone properties, you eventually need to trigger their accessors in case of CSS. So, if you want to copy properties between two CSS objects, you can just go through the for/in that will trigger setters in the target, but if you are copying/cloning any other property, the link I’ve suggested is the actual ES7 proposal to do that, and by standard that should just work unless the target has properties that are not configurable, in which case you’ll have a silent no-op which might be what gave you troubles. A live example would be nice though ’cause I think I might miss what you are talking about, reading twice your post 🙂

  • Giuseppe Gurgone

    As WebReflection said Object.keys(from) should work just fine.
    http://codepen.io/giuseppegurgone/pen/EjzKbV?editors=001

    • LOL, the Missing The Point Award 2015 goes to you.

      Andrea was not suggesting Object.keys() as some sort of alternative, he was suggesting it to improve a part of the code. Read the post more carefully.

      • Giuseppe Gurgone

        woops you want to copy accessors. I misread the fundamental part my bad 🙂

      • Anna Myers

        LOL, the Missing Tact Award 2015 should go to Lea Verou. Read this comment carefully.

  • Bnaya Peretz

    Page layout problem @ Chorme dev

  • AndrewEddie

    Do you have a set of unit tests that assert all the behaviours that you want to cover?

  • Glenn

    Thanks for this Lea. I completely agree with taking a much lighter approach to web dev when possible and have wrote about it myself – and am also working on a library that I call a “foundation” (as opposed to framework) – a set of foundational “helper functions” and modules of which one can include their **needed subset**. I’ll be grabbing this extend for it if you don’t mind!

    I think in the final version you accidentally left out the enclosing for block: “`for (var property in from) {…}“`

  • Hey Lea, nice post! I made an npm module based off your example. If you have any thoughts, not hesitate to contribute there!

    https://github.com/fnky/extend-accessors

    ~fnky

  • pottyTrained

    ello , just wanted to say ppi.lv domain is available.

  • Nisha Janarthan

    Very much useful information which helped me gain something knowledgeable. By web development institute in Chennai

  • Maybe?


    function extend(from, to) {
    for (var property in from) {
    var descriptor = Object.getOwnPropertyDescriptor(from, property) || { value: from[property] };
    Object.defineProperty(to, property, descriptor);
    }
    return to;
    }

  • Mariss Aryan

    Much informative things are in this blog. Thanks for giving us such valuable blog.

    IOSH safety course in Kuwait

  • Accessors in javascript seem pretty strange, could you point out an example where they would be useful ?