The New JavaScript Problem

The new keyword in JavaScript is very straightforward to use:

function Rectangle (x, y) {
this.x = x;
this.y = y;
}

var rect = new Rectangle(2, 3);

rect.x
//=> 2

When a function is called with the new keyword, a new object is created and …


This content originally appeared on raganwald.com and was authored by Reginald Braithwaite

The new keyword in JavaScript is very straightforward to use:

function Rectangle (x, y) {
  this.x = x;
  this.y = y;
}

var rect = new Rectangle(2, 3);

rect.x
  //=> 2

When a function is called with the new keyword, a new object is created and that object becomes the context of the function call. That context is available within the function using the this keyword. If the function does not explicitly return anything in its body, the result of evaluating the function with the new keyword will be the new object.

By default, all functions have a prototype property, and that prototype defaults to being a new, empty object:

Rectangle.prototype
  //=> {}

Each function gets its own distinct prototype object:

function A () {}
function B () {}
A.prototype === B.prototype
  //=> false

A.prototype.foo = 'SNAFU';
B.prototype.foo = 'FUBAR';
A.prototype
  //=> { foo: 'SNAFU' }
B.prototype
  //=> { foo: 'FUBAR' }

When you create a new object using the new keyword and a function, there is a special relationship established between the object and the function’s prototype:

var a = new A();
Object.getPrototypeOf(a)
  //=> { foo: 'SNAFU' }
A.prototype.isPrototypeOf(a)
  //=> true

That relationship is established at the time the object is created. If we replace the function’s prototype, it doesn’t affect the objects already created:

A.prototype = { FUBAR: 'foo' }
A.prototype.isPrototypeOf(a)
  //=> false

The special relationship goes further: Objects inherit the properties of their prototypes:

var b = new B();

b.foo
  //=> 'FUBAR'

JavaScript doesn’t always show inherited properties to us in the console:

b
  //=> {}

But they’re still there!

b.foo
  //=> 'FUBAR'

Prototypes are very useful for methods:

function Rectangle (x, y) {
  this.x = x;
  this.y = y;
}
Rectangle.prototype.area = function () {
  return this.x * this.y;
}

var twoByThree = new Rectangle(2, 3);
twoByThree.area()
  //=> 6

var threeByFive = new Rectangle(3, 5);
threeByFive.area()
  //=> 15

Reassigning prototypes allows us to share prototypes:

function Square (x) {
  this.x = this.y = x;
}
Square.prototype = Rectangle.prototype;

var fourByFour = new Square(4);
fourByFour.area()
  //=> 16

This might or might not be a bad idea. Another way to accomplish the same objective is to note that a prototype can be any object, including an object created with a function. So:

Square.prototype = new Rectangle();

Now Square has its own prototype that inherits from Rectangle’s prototype, but it isn’t the same object:

Square.prototype === Rectangle.prototype
  //=> false

But we get the same behaviour we wanted:

var fourByFour = new Square(4);
fourByFour.area()
  //=> 16

Separating the two prototypes is superior if there is any difference between a square and a rectangle aside from how they are initialized. For example, if you ever want to write something like this:

Rectange.prototype.toString = function () {
  return "I am a " + this.x + " by " + this.y + " rectangle";
}

Square.prototype.toString = function () {
  return "I am a " + this.x + " by " + this.y + " square";
}

Then you need to have separate prototypes. On the other hand, you might decide to write this:

function GoldenRectangle (x) {
  this.x = x;
  this.y = 1.6 * x;
}
GoldenRectangle.prototype = Rectangle.prototype;

Having multiple JavaScript functions share the same prototype serves the same purpose as one Java class having multiple constructor functions.

Object.create

Constructors are not the only way to create JavaScript objects. Object.create creates a new JavaScript object and permits you to specify the prototype:

var myPrototype = {
  name: "My Prototype"
}

var myObject = Object.create(myPrototype);
Object.getPrototypeOf(myObject)
  //=> { name: 'My Prototype' }

Now that we know this, we can see that the new keyword is a kind of shorthand for:

var pseudoNew = variadic(function (constructor, args) {
  var newObject = Object.create(constructor.prototype);
  var returnedObject = constructor.apply(newObject, args);
  if (typeof(returnedObject) ===  'undefined') {
    return newObject;
  }
  else return returnedObject
});

Using Object.create, we can be explicit about what objects are create with what prototypes. Here we are using a .create method:

var Circle = {
  prototype: {
    area: function () {
      return Math.PI * this.radius * this.radius;
    }
  },
  create: function (radius) {
    var circle = Object.create(this.prototype);
    circle.radius = radius;
    return circle;
  }
}

var fiver = Circle.create(5);
fiver.area()
  //=> 78.53981633974483

So, you can use new or you can use Object.create to create new objects with prototypes. So far, so good.

But there are some clouds on the horizon.

why instanceof might be a problem

JavaScript provides an instanceof keyword. It appears at first to be useful:

fourByFour instanceof Rectangle
  //=> true

It is a kind of shorthand for:

function instanceOf(object, constructor) {
  return constructor.prototype.isPrototypeOf(object)
}

The trouble with instanceof is that it is unreliable whenever you use Object.create instead of the new keyword, or when you replace the prototype of a function used as a constructor. Semantically, the item of interest in the prototype, and therefore if you use Object.create instead of the new keyword, you must also use .isPrototypeOf instead of instanceof.

Most OO programmers prefer using polymorphism to explicitly testing instanceof. Wide use of explicit type testing is generally a design smell, but nevertheless it is a useful tool in some circumstances.

handling the case when we don’t use new

When we choose to write our code to use the new keyword, we may want to consider taking precautions. As noted in Effective JavaScript, traditional constructors will usually fail in ugly ways if we accidentally invoke them without using the new keyword:

function Circle (radius) {
  this.radius = radius;
}
Circle.prototype.area = function () {
  return Math.PI * this.radius * this.radius;
}

new Circle(2).area()
  //=> 12.566370614359172

Circle(2).area()
  //=> TypeError: Cannot call method 'area' of undefined

We can, of course, not do that. We can also code defensively:

function Circle (radius) {
  var newObject = Circle.prototype.isPrototypeOf(this)
                  ? this
                  : new Circle();
  newObject.radius = radius;
  return newObject;
}
Circle.prototype.area = function () {
  return Math.PI * this.radius * this.radius;
}

new Circle(2).area()
  //=> 12.566370614359172

Circle(2).area()
  //=> 12.566370614359172

Our rewritten constructor handles the case when this is not a new Circle instance by creating one before doing the initializing. It also explicitly returns the new object instead of relying on new to infer what we want when we don’t return anything.

We now have a function we expected would be used with the new keyword, but it also handles the case where the new keyword isn’t used.

handling the case when we do use new

It goes the other way as well. Sometimes we write a function that we don’t expect to be used with the new keyword, but we later discover that we’d like to invoke it with the new keyword. For example, we might choose to write a function decorator, as one might find in this essay or in libraries like Method Combinators:

function before (fn, beforeAdvice) {
  return unvariadic(fn.length, function (args) {
    beforeAdvice.apply(this, arguments);
    return fn.apply(this, arguments);
  });
}

function add (x, y) {
  return x + y;
}

add(1, 1)
  //=> 2

function preparation () {
  console.log('Hang on while I get a piece of paper. Ok, I\'m ready!');
}

var newAdd = before(add, preparation);

newAdd(2, 2)
  //=>
    Hang on while I get a piece of paper. Ok, I'm ready!
    4

Alas, our decorator breaks down if we use it with a traditional constructor:

function Square (side) {
  this.side = side;
}
Square.prototype.area = function () {
  return this.side * this.side;
}

new Square(1).area()
  //=> 1

NewSquare = before(Square, preparation);

new NewSquare(2).area()
  //=>
    Hang on while I get a piece of paper. Ok, I'm ready!
    TypeError: Object [object Object] has no method 'area'

Again, we can not do that: When we construct objects in our own functions or methods using Object.create, we don’t need to take special precautions when writing decorators:

var OurSquare = {
  create: function (side) {
    var newObject = Object.create(OurSquare.prototype);
    newObject.side = side;
    return newObject;
  },
  prototype: {
    area: function () {
      return this.side * this.side;
    }
  }
}

OurSquare.create(3).area()
  //=> 9

OurSquare.create = before(OurSquare.create, preparation);

OurSquare.create(4).area()
  //=>
    Hang on while I get a piece of paper. Ok, I'm ready!
    16

But if we wish to accommodate the new keyword when writing things like decorators, we can take precautions:

// shim for Object.setPrototypeOf
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

function defensiveBefore (fn, beforeAdvice) {
  return function wrapped () {
    if (Object.getPrototypeOf(this) === wrapped.prototype) {
      Object.setPrototypeOf(this, fn.prototype);
    }
    beforeAdvice.apply(this, arguments);
    return fn.apply(this, arguments);
  };
}

var NewestSquare = defensiveBefore(Square, preparation);
  //=>
    Hang on while I get a piece of paper. Ok, I'm ready!
    25

so what’s the problem?

The trouble with approaches like this is that precautions can pile up on top of precautions, until our original intent has been obscured. One solution is don’t do that, as in, don’t try to decorate constructor functions, and be sure you know when a function is designed to create an object with new and when it is called normally.

The other possibility is don’t do that, as in, don’t use new: Write functions and methods that explicitly call Object.create, and use .isPrototypeOf instead of instanceof. This is an equally straightforward approach, and resistant to the edge cases surrounding the new keyword when writing modern JavaScript.

(discuss on reddit)


This content originally appeared on raganwald.com and was authored by Reginald Braithwaite


Print Share Comment Cite Upload Translate Updates
APA

Reginald Braithwaite | Sciencx (2014-01-14T00:00:00+00:00) The New JavaScript Problem. Retrieved from https://www.scien.cx/2014/01/14/the-new-javascript-problem-2/

MLA
" » The New JavaScript Problem." Reginald Braithwaite | Sciencx - Tuesday January 14, 2014, https://www.scien.cx/2014/01/14/the-new-javascript-problem-2/
HARVARD
Reginald Braithwaite | Sciencx Tuesday January 14, 2014 » The New JavaScript Problem., viewed ,<https://www.scien.cx/2014/01/14/the-new-javascript-problem-2/>
VANCOUVER
Reginald Braithwaite | Sciencx - » The New JavaScript Problem. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2014/01/14/the-new-javascript-problem-2/
CHICAGO
" » The New JavaScript Problem." Reginald Braithwaite | Sciencx - Accessed . https://www.scien.cx/2014/01/14/the-new-javascript-problem-2/
IEEE
" » The New JavaScript Problem." Reginald Braithwaite | Sciencx [Online]. Available: https://www.scien.cx/2014/01/14/the-new-javascript-problem-2/. [Accessed: ]
rf:citation
» The New JavaScript Problem | Reginald Braithwaite | Sciencx | https://www.scien.cx/2014/01/14/the-new-javascript-problem-2/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.