Inheritance and Object.create

There’s been a lot of discussion lately about using Object.create() instead of constructor functions and the new keyword for JavaScript inheritance. Douglas Crockford’s influential article at This write up argues that Object.create() aligns better with JavaScript’s prototypal nature. I agree that creating an object and using it as a prototype for another object feels more natural with Object.create().

However, as comfortable as I am with JavaScript’s flexibility, I still find myself drawn to the class-based paradigm in many situations. I appreciate classes as object templates and for identifying an instance’s origin through myInstance.constructor.

JavaScript luckily allows both approaches. We can maintain structure with class-like patterns while retaining the freedom to use prototypes for augmenting “classes” or instances. For me, this makes Object.create() a helpful tool, but not a complete replacement for constructor functions.

I’m not alone in this view. Articles like [1 and 2 by well-respected developers echo my thoughts. They suggest that Object.create() can enhance class-based inheritance in JavaScript. Ben Nadel’s article (URL_PLACEHOLDER_3) explores a “classical design” solely with Object.create() and concludes that it’s not ideal.

Both articles propose continuing to define constructor functions and adding methods to constructorFunction.prototype, but using Child.prototype = Object.create(Person.prototype); to establish the inheritance chain, instead of Child.prototype = new Person();. This avoids calling the Person constructor when setting up inheritance, a minor optimization, but an improvement nonetheless.

What these articles overlook is setting the constructor property on prototype objects within the chain. In traditional inheritance, we do:

1
2
Child.prototype = new Person();  
Child.prototype.constructor = Child;

With the Object.create() approach, it becomes:

1
Child.prototype = Object.create(Person.prototype, { constructor: {value: Child} });

This ensures that myInstance.constructor retrieves the correct function used to create the instance, similar to Object.GetType() in C#. One might argue this is a static approach not well-suited for a dynamic language like JavaScript, where objects can be freely modified during runtime. And while duck typing is generally favored in JavaScript, having the constructor property can be beneficial in certain scenarios.

Here’s how the code might look:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Shape "class"
function Shape() {
  this.x = 0;
  this.y = 0;
  console.log('Shape constructor called');
}

Shape.prototype = {
  constructor: Shape,
  move: function(x, y) {
    this.x += x;
    this.y += y;
 console.log("moving");
  },
};

console.log("Shape.prototype.constructor.name: " + Shape.prototype.constructor.name);
var s1 = new Shape();
console.log("s1.constructor: " + s1.constructor.name);

// Rectangle "class"
function Rectangle() {
  console.log('Rectangle constructor called');
  Shape.call(this);
}
//very important, remember we have to use the new "properties syntax"
Rectangle.prototype = Object.create(Shape.prototype, {
 constructor: {value: Rectangle},
 rectangleOperation: {value: function(){
  console.log("rectangleOperation");
 }}
});

An interesting point from the daily.js article is that Object.create() offers a unique way to create objects without a prototype chain (where [[Prototype]] is null). You might think setting constructorFunction.prototype to null would achieve the same, but it actually makes the instance’s [[Prototype]] point to Object.prototype. This behavior is likely detailed somewhere in the ECMAScript specification.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function Shape() {
}
 
//this is pretty interesting, even if we define the Shape prototype as null, an instance object created with it ends up getting a [[prototype]], the Object.[[prototype]]

Shape.prototype = null;
var s1 = new Shape();
if (!Object.getPrototypeOf(s1))
 console.log("s1.__proto__ is null"); //prints nothing

if (Object.getPrototypeOf(s1) === Object.prototype)
 console.log("s1.__proto__ === Object.prototype"); //this is true

Another practical use of Object.create() is mapping “data objects” to “full objects.” Objects usually possess both data and behavior, but when transmitted over the network (like in JSON format from an Ajax call), we only receive the data. To regain full functionality, we need to map this data back to an object with an appropriate prototype.

This example demonstrates this concept (using a fieldsToProperties function because Object.create() expects an object with properties, not just data fields):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//interesting use of Object.create to map a "data object" returned from the server to a full object
function fieldsToProperties(obj){
 var res = {};
 Object.keys(obj).forEach(function(key){
  res[key] = {value: obj[key]};
 });
 return res;
}

//Employee "class"
function Employee(nm){
 this.name = nm;
};

Employee.prototype = {
 constructor: Employee, 
 generatePayroll: function(){
  console.log("generating payroll for " + this.name);
 }
};

//let's say I receive this Employee object via an Ajax call, so I have only data and want to add the Employee functionality to it
var e1 = {
 name: "xuan",
 age: 30,
 city: "Uvieu"
};

e1 = Object.create(Employee.prototype, fieldsToProperties(e1));

e1.generatePayroll();

Object.create

Licensed under CC BY-NC-SA 4.0
Last updated on Jul 06, 2023 03:56 +0100