Using "Object.create" instead of "new"

199,496

Solution 1

With only one level of inheritance, your example may not let you see the real benefits of Object.create.

This methods allows you to easily implement differential inheritance, where objects can directly inherit from other objects.

On your userB example, I don't think that your init method should be public or even exist, if you call again this method on an existing object instance, the id and name properties will change.

Object.create lets you initialize object properties using its second argument, e.g.:

var userB = {
  sayHello: function() {
    console.log('Hello '+ this.name);
  }
};

var bob = Object.create(userB, {
  'id' : {
    value: MY_GLOBAL.nextId(),
    enumerable:true // writable:false, configurable(deletable):false by default
  },
  'name': {
    value: 'Bob',
    enumerable: true
  }
});

As you can see, the properties can be initialized on the second argument of Object.create, with an object literal using a syntax similar to the used by the Object.defineProperties and Object.defineProperty methods.

It lets you set the property attributes (enumerable, writable, or configurable), which can be really useful.

Solution 2

There is really no advantage in using Object.create(...) over new object.

Those advocating this method generally state rather ambiguous advantages: "scalability", or "more natural to JavaScript" etc.

However, I have yet to see a concrete example that shows that Object.create has any advantages over using new. On the contrary there are known problems with it. Sam Elsamman describes what happens when there are nested objects and Object.create(...) is used:

var Animal = {
    traits: {},
}
var lion = Object.create(Animal);
lion.traits.legs = 4;
var bird = Object.create(Animal);
bird.traits.legs = 2;
alert(lion.traits.legs) // shows 2!!!

This occurs because Object.create(...) advocates a practice where data is used to create new objects; here the Animal datum becomes part of the prototype of lion and bird, and causes problems as it is shared. When using new the prototypal inheritance is explicit:

function Animal() {
    this.traits = {};
}

function Lion() { }
Lion.prototype = new Animal();
function Bird() { }
Bird.prototype = new Animal();

var lion = new Lion();
lion.traits.legs = 4;
var bird = new Bird();
bird.traits.legs = 2;
alert(lion.traits.legs) // now shows 4

Regarding, the optional property attributes that are passed into Object.create(...), these can be added using Object.defineProperties(...).

Solution 3

Object.create is not yet standard on several browsers, for example IE8, Opera v11.5, Konq 4.3 do not have it. You can use Douglas Crockford's version of Object.create for those browsers but this doesn't include the second 'initialisation object' parameter used in CMS's answer.

For cross browser code one way to get object initialisation in the meantime is to customise Crockford's Object.create. Here is one method:-

Object.build = function(o) {
   var initArgs = Array.prototype.slice.call(arguments,1)
   function F() {
      if((typeof o.init === 'function') && initArgs.length) {
         o.init.apply(this,initArgs)
      }
   }
   F.prototype = o
   return new F()
}

This maintains Crockford prototypal inheritance, and also checks for any init method in the object, then runs it with your parameter(s), like say new man('John','Smith'). Your code then becomes:-

MY_GLOBAL = {i: 1, nextId: function(){return this.i++}}  // For example

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.build(userB, 'Bob');  // Different from your code
bob.sayHello();

So bob inherits the sayHello method and now has own properties id=1 and name='Bob'. These properties are both writable and enumerable of course. This is also a much simpler way to initialise than for ECMA Object.create especially if you aren't concerned about the writable, enumerable and configurable attributes.

For initialisation without an init method the following Crockford mod could be used:-

Object.gen = function(o) {
   var makeArgs = arguments 
   function F() {
      var prop, i=1, arg, val
      for(prop in o) {
         if(!o.hasOwnProperty(prop)) continue
         val = o[prop]
         arg = makeArgs[i++]
         if(typeof arg === 'undefined') break
         this[prop] = arg
      }
   }
   F.prototype = o
   return new F()
}

This fills the userB own properties, in the order they are defined, using the Object.gen parameters from left to right after the userB parameter. It uses the for(prop in o) loop so, by ECMA standards, the order of property enumeration cannot be guaranteed the same as the order of property definition. However, several code examples tested on (4) major browsers show they are the same, provided the hasOwnProperty filter is used, and sometimes even if not.

MY_GLOBAL = {i: 1, nextId: function(){return this.i++}};  // For example

var userB = {
   name: null,
   id: null,
   sayHello: function() {
      console.log('Hello '+ this.name);
   }
}

var bob = Object.gen(userB, 'Bob', MY_GLOBAL.nextId());

Somewhat simpler I would say than Object.build since userB does not need an init method. Also userB is not specifically a constructor but looks like a normal singleton object. So with this method you can construct and initialise from normal plain objects.

Solution 4

TL;DR:

new Computer() will invoke the constructor function Computer(){} for one time, while Object.create(Computer.prototype) won't.

All the advantages are based on this point.

Sidenote about performance: Constructor invoking like new Computer() is heavily optimized by the engine, so it may be even faster than Object.create.

Solution 5

You could make the init method return this, and then chain the calls together, like this:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
        return this;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};

var bob = Object.create(userB).init('Bob');
Share:
199,496
Graham King
Author by

Graham King

Updated on July 08, 2022

Comments

  • Graham King
    Graham King almost 2 years

    Javascript 1.9.3 / ECMAScript 5 introduces Object.create, which Douglas Crockford amongst others has been advocating for a long time. How do I replace new in the code below with Object.create?

    var UserA = function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    }
    UserA.prototype.sayHello = function() {
        console.log('Hello '+ this.name);
    }
    var bob = new UserA('bob');
    bob.sayHello();
    

    (Assume MY_GLOBAL.nextId exists).

    The best I can come up with is:

    var userB = {
        init: function(nameParam) {
            this.id = MY_GLOBAL.nextId();
            this.name = nameParam;
        },
        sayHello: function() {
            console.log('Hello '+ this.name);
        }
    };
    var bob = Object.create(userB);
    bob.init('Bob');
    bob.sayHello();
    

    There doesn't seem to be any advantage, so I think I'm not getting it. I'm probably being too neo-classical. How should I use Object.create to create user 'bob'?

  • Graham King
    Graham King about 14 years
    1. Thanks for the pointer to differential inheritance. 2. Does this mean no more constructors? I need to remember to set 'id' to MY_GLOBAL.nextId() every time I create a user?
  • Christian C. Salvadó
    Christian C. Salvadó about 14 years
    You're welcome @Graham, you're right, no more constructors needed with this method, although the currently available implementations on Firefox 3.7apre5, the latest WebKit Nightly builds and Chrome 5 Beta, are not so fast compared with plain old constructors, hopefully this will change in the near future. For the object creation, you could create a factory function(i.e. function createUser(name) { ... }, with all the needed logic to create your user objects within with Object.create.
  • Daniel Earwicker
    Daniel Earwicker almost 13 years
    Re: no more constructors: Normally you'd write an ordinary function to be the "factory" for objects. Internally it would use Object.create to make a blank object, and then modify it as necessary before returning it. The caller of that factory doesn't have to remember the prefix new.
  • Daniel Earwicker
    Daniel Earwicker almost 13 years
    Why not just have UserB say var f = Object.create(userPrototype); f.init(name); return f; ?
  • ryanve
    ryanve over 12 years
    There is a polyfill for Object.create in the ES5 Shim github.com/kriskowal/es5-shim
  • ryanve
    ryanve over 12 years
    @CMS When would or wouldn't you want to set enumerable to true?
  • amiuhle
    amiuhle about 12 years
    @GrahamKing You could use a closure to init your objects: jsfiddle.net/Prqdt
  • kybernetikos
    kybernetikos over 11 years
    Calling this clone is confusing to me (and probably many others). To most people a clone method implies that changes to the original would not affect the clone.
  • Kos
    Kos over 11 years
    I disagree. Object.create neither enforces nor encourages the practice of using prototypes as any kind of "storage for default data values" as the linked article seems to suggest. Proper data initialization is the responsibility of whoever creates a particular object (like a factory or builder in the OO design). (Inheriting data rather than behaviour is feasible in JS, but not a common scenario.)
  • basos
    basos over 11 years
    Understood, I amended the answer to account only for source objects considered immutable.
  • plediii
    plediii over 11 years
    As long as you understand that the argument to Object.create is supposed to be the prototype, this problem shouldn't come up. Obviously you can get the same bad behavior when using new if you say Animal.prototype.traits = {}; The only reason it's clear you shouldn't do that is that you understand how javascript prototypes work.
  • Noel Abrahams
    Noel Abrahams about 11 years
    God god! So many downvotes for providing the right answer :-) The point is Object.create does not permit a mechanism for constructor arguments so one is forced to extend "data". Now this data may contain nested objects, which leads to the problem above. With prototypal inheritance on the other hand we only run into this problem if we were to explicitly write out Animal.prototype.traits = {};. One method is implicit the other explicit. Don't chose the one that leads to problems.
  • zod
    zod almost 11 years
    In the second example, the two objects don't share the common prototype (although they do share the next one in the chain). If you want both animals to have their own traits property, you could apply the Animal function to both bird and lion, and they will each get a traits property directly (no prototype, and not shared). There may times when you want things to be shared (like you don't want to recreate all the functions each time a new object is created), and I think that is when you make use of the prototype.
  • Geek
    Geek almost 11 years
    @CMS What is differential inheritance?Why is it called so?
  • Matt
    Matt almost 11 years
    Great article! I've added a JSFiddle, so you have a working example for both Object.build and Object.gen in one file. Also, I've added some semicolons which were missing (available in JSFiddle only).
  • tne
    tne over 10 years
    Problem with using new with a subtype this way is that you never want to specify arguments for the supertype ctor, since you don't have the data at this point. Suddenly you need to insert a conditional in your supertype ctor to check if the arguments are provided or not; but what if you don't want a default ctor? There is no way that I know of to determine whether the call was done to setup the prototype of a subtype or to setup the data of the supertype in the subtype ctor.
  • d13
    d13 over 10 years
    Please see this post for a simple solution to the 2 legged lion. Here's some working code to illustrate it
  • oligofren
    oligofren over 10 years
    init might be called several times. nothing preventing that.
  • linstantnoodles
    linstantnoodles about 10 years
    @CMS ", if you call again this method on an existing object instance, the id and name properties will change.". Wait, how? this is referring to the new object with its own name and id properties. Why would calling init on an different object with the same prototype change those properties?
  • MindJuice
    MindJuice about 10 years
    I would suggest reading this article by Kyle Simpson. All three parts are interesting, but part 3 is key. If after reading those you still think "new" is better than Object.create(), then there is no hope for you! :) davidwalsh.name/javascript-objects-deconstruction
  • UpTheCreek
    UpTheCreek almost 10 years
    That jsperf appears to be gone.
  • Skystrider
    Skystrider over 8 years
    Passing in the 2nd param a properties object does not initialize the new object to have those properties. I have to set them directly, line by line. var model = Object.create(parent); model.someFunction = someFunction; function someFunction(){...}
  • rixo
    rixo over 8 years
    To be fair, the second example should be completed so: var clarence = new Lion(); clarence.traits.legs = 3; console.log(lion.traits.legs) // 3, shit!
  • Mehdi Maujood
    Mehdi Maujood over 8 years
    If you were using Object.create with the proper conceptual understanding of prototypes, you would say "Of course it doesn't work that way! The shared prototype is a feature, not a bug!"
  • Andy
    Andy about 7 years
    Cloning objects repeatedly this way will quickly get you into trouble, as you'll have an object with a really long prototype chain that keeps all those old objects in memory. I'll sometimes use Object.create in testing to make mocks that override some properties of an instance of a class while keeping others the same. But I rarely use it.
  • albanx
    albanx about 7 years
    it is not inheritance, it is object composition
  • klewis
    klewis over 6 years
    To clarify, we can share multiple methods from multiple prototype objects with the help of Object.Create. This sharing flexibility is clearly an "advantage" over new, and part of the beauty of JavaScript.
  • Adamantus
    Adamantus over 5 years
    What javascript proves is that if you build the foundations out of crap, all subsequent layers of the house will also be crap.
  • jk7
    jk7 about 5 years
    This is intriguing. Could you extend the example to show how you would implement setters? Or would Object.freeze() prevent that?
  • p0wdr.com
    p0wdr.com about 5 years
    The freeze is just stopping the runtime modification of the "class". I've extended the example of the parent with a local variable in the closure and a getter and setter to show managing the "private" variable in the closure
  • Kamafeather
    Kamafeather over 4 years
    @ryanve when the object has some private property that you don't want to be enumerated (e.g. in a for in where you don't want to list object metadata but just actual data properties). See MDN Enumerability
  • Kamafeather
    Kamafeather over 4 years
  • Kamafeather
    Kamafeather over 4 years
    @linstantnoodles he didn't write "different object" but "existing"; I think the emphasis is on "again", since the public init method replaces the (kind of private) constructor, but might be called multiple times and cause undesired results; initialisation should happen just once.
  • dev_khan
    dev_khan over 4 years
    I checked the above code and may be Object.create has evolved but for now Object.create is not cloning anything and the result of the code var bObj = Object.create(anObj); console.log(bObj); is {} means no properties are copied.
  • Dalibor
    Dalibor over 4 years
    This is bad example of using Object,create to solve combination inheritance (constructor stealing + prototype chaining) major failing which is that parent constructor is called twice. Object.create creates new object using existing object as prototype which means one more prototype in the chain. The mentioned failing is solved using parasitic combination inheritance (constructor stealing + hybrid prototype chaining). We only need to copy parent prototype and it can be done like this: var prototype = new Object(SiteMember.prototype); prototype.constructor = Guest; Guest.prototype = prototype;
  • Shardul
    Shardul about 4 years
    Good catch - there was a typo. Fixed it now.
  • S Meaden
    S Meaden about 3 years
    Re Feedback, can you give code that creates an instance please? I feel like this is 'server' code and that no 'client' code has been given.
  • p0wdr.com
    p0wdr.com over 2 years
    The instance creation happens at the end of the example with the following lines let myQuad = new Quad(); let myCar = new Car4wd(); this is client code, it uses the window object to store the types/classes
  • Nils Lindemann
    Nils Lindemann over 2 years
    One can further do create = Object.create.bind(Object); log = console.log.bind(console) and then one can just write create(...) and log(...). See here for what .bind(...) does.