Skip to content

Instantly share code, notes, and snippets.

@dherman
Created July 28, 2012 18:13
Show Gist options
  • Save dherman/3194234 to your computer and use it in GitHub Desktop.
Save dherman/3194234 to your computer and use it in GitHub Desktop.
What everyone kinda wishes Object.create had been
Object.beget = (function() {
var create = Object.create,
defineProperty = Object.defineProperty;
// see https://gist.github.com/3194222
var hasOwn = Object.prototype.hasOwnProperty.unselfish();
return function beget(proto, own) {
var result = create(proto);
var descriptor = {
enumerable: true,
writable: true,
configurable: true
};
for (var key in own) {
if (!hasOwn(own, key))
continue;
descriptor.value = own[key];
defineProperty(result, key, descriptor);
}
return result;
};
})();
var root = {
method: function() { return [this.x, this.y] }
};
var o = Object.beget(root, { x: 0, y: 22 });
console.log(o.method()); // [0, 22]
@isaacs
Copy link

isaacs commented Jul 28, 2012

So, Object.beget(root, { x: 0, y: 22 }) is kinda like Object.create(root, {x: { value: 0, writable: true, configurable: true, enumerable: true}, y:{value: 22, configurable: true, writable:true, enumerable:true}}})?

If so, yes, please.

@dherman
Copy link
Author

dherman commented Jul 28, 2012

@isaacs: Yes, exactly.

Dave

@polotek
Copy link

polotek commented Jul 28, 2012

I like this. I've written this version more than once to use in my own stuff. Why is it so different from the way people use Object.extend? Because it also sets the proto? I think of that as an extra nicety if you need it. So maybe we can kind of get the best of both worlds by having the proto as an optional second param.

Object.clone = (function() {
    var create = Object.create,
        defineProperty = Object.defineProperty;

    // see https://gist.github.com/3194222
    var hasOwn = Object.prototype.hasOwnProperty.unselfish();

    return function clone(own, proto) {
        var result;
        if(proto && typeof proto === 'object') {
            result = create(proto);
        } else {
            result = {};
        }

        var descriptor = {
            enumerable: true,
            writable: true,
            configurable: true
        };

        for (var key in own) {
            if (!hasOwn(own, key))
                continue;

            descriptor.value = own[key];
            defineProperty(result, key, descriptor);
        }

        return result;
    };
})();

var root = {
    method: function() { return [this.x, this.y] }
};

var o = Object.clone({ x: 0, y: 22 });
console.log(o.y); // 22
o.method === undefined;

var o2 = Object.clone({ x: 0, y: 22}, root);

console.log(o2.y); // 22
console.log(o2.method()); // [0, 22]

Without the second parameter it's (almost) the same as underscore's _.clone. Which is just a single case of _.extend. The differences are minor enough that I think people would embrace this if it was standardized. Please excuse the bikeshedding by changing to Object.clone. It's just that beget doesn't mean anything to most people.

@polotek
Copy link

polotek commented Jul 28, 2012

Actually let me qualify that. The extend methods from popular libraries don't do a hasOwnProperty check, which is not a not so minor difference. Instead I should ask whether you and others think this is significant enough. My feeling is that the own property check won't change anything for 90% of users. And the other 10% should probably be using a more sophisticated method.

@dherman
Copy link
Author

dherman commented Jul 28, 2012

@polotek: I was actually thinking about the same idea! The reason it's different from extend is that extend usually takes a target object to modify, rather than returning a new object. But I was thinking about clone too, with the arguments reversed from Object.create as you suggest.

One downside I see is that it's inconsistent with the argument order of the existing Object.create. And that also makes it less convenient for the common case of creating a proto-less dictionary object (Object.create(null) vs Object.clone({}, null)). But I guess you could make the case that Object.create(null) is a little more idiomatic since you're not really cloning anything.

A possible extension, suggested by François Rémy, is a third (and optional) argument containing property descriptors, so that you can provide some properties with more control. Undecided yet about my opinion on that.

Bugfix: I think you want to allow typeof proto to be 'function' as well.

Dave

@dherman
Copy link
Author

dherman commented Jul 28, 2012

Not sure about hasOwnProperty, actually. This is one place where the name and argument order may actually affect the intended semantics: a clone method probably leads people to expect that the object to clone could be anything, which might have enumerable properties anywhere up its prototype inheritance chain. Whereas the original API I described was expected just to be an object literal (and therefore any inherited enumerable properties would be a result of naughty Object.prototype extensions).

Dave

@polotek
Copy link

polotek commented Jul 28, 2012

Take a look at my little protochains lib I was experimenting with. I had the same idea about adding a third param for extra properties. It has several other things I've been playing with too.

https://github.com/polotek/procstreams/blob/master/protochains.js

I don't think people will expect the complicated version of clone. I would dare to say that most of the time, people aren't dealing with complex objects. They are cloning data objects or shallow instance objects. The idea of a "deep clone" suggests more sophistication. It means you know you're dealing with complex objects. You know they may have significant proto chains. And you know whether you want those proto properties or not. And if that's the case, you should put together a sophisticated method that works the way you want, by composing these simpler methods. You should have everything you need with Object.create, Object.clone, and perhaps one of the common extend methods. My feeling is that these are simple to understand individually, but still give you lots of power and flexibility when combined.

@chevcast
Copy link

When I first started learning about Object.create I actually assumed this was how it worked. I was bewildered when things weren't working as I expected. I do wish this method were simpler.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment