7

I can create a class that does not inherit from Object.prototype using the older syntax.

function Shape(x, y, width, height) {
  this.x = x,
  this.y = y,
  this.width = width,
  this.height = height;
}

Shape.prototype = Object.create(null, {
  constructor: {
    configurable: true,
    writable: true,
    value: Shape
  },
  move: {
    configurable: true,
    writable: true,
    value: function (x, y) {
      this.x += x,
      this.y += y;
    }
  }
});

var rect = new Shape(0, 0, 4, 2);
console.log(Object.getPrototypeOf(rect) === Shape.prototype);
console.log(Object.getPrototypeOf(Object.getPrototypeOf(rect)) !== Object.prototype); //inheritance

How can I do this using ES6 classes?

class Shape {
  constructor(x, y, width, height) {
    this.x = x,
    this.y = y,
    this.width = width,
    this.height = height;
  }

  move(x, y) {
    this.x += x,
    this.y += y;
  }
}

var rect = new Shape(0, 0, 4, 2);
console.log(Object.getPrototypeOf(rect) === Shape.prototype);
console.log(Object.getPrototypeOf(Object.getPrototypeOf(rect)) === Object.prototype); // inheritance
Frank Tan
  • 4,234
  • 2
  • 19
  • 29
Michael Theriot
  • 994
  • 7
  • 13

2 Answers2

7

You can use extends null.

Note the class itself will still inherit from Function.prototype, not from null. So you will be able to use function methods on the class.

But be aware that, when using an extends clause, you must either initialize this before using it by calling super, or don't use this and return an object at the end.

In this case you can't initialize this using super because Function.prototype is not a constructor. So you will have to use Object.create to create the object that will become the instance.

class Shape extends null {
  constructor(x, y) {
    // Use `that` instead of `this`, and return it at the end
    var that = Object.create(new.target.prototype);
    that.x = x;
    that.y = y;
    return that;
  }
  move(x, y) {
    this.x += x;
    this.y += y;
  }
}
var rect = new Shape(0, 0);
console.log(rect);
console.log(Object.getPrototypeOf(rect) === Shape.prototype);
console.log(Object.getPrototypeOf(Shape.prototype) === null);
console.log(Object.getPrototypeOf(Shape) === Function.prototype);

new.target will be the function that is being instantiated. This can be Shape itself, or another function that extends it. This is useful to allow Shape to be extendable.

class Shape extends null {
  constructor(x, y) {
    // Use `that` instead of `this`, and return it at the end
    var that = Object.create(new.target.prototype);
    that.x = x;
    that.y = y;
    return that;
  }
  move(x, y) {
    this.x += x;
    this.y += y;
  }
}
class BestShape extends Shape {
  constructor(...args) {
    super(...args);
    this.best = true;
  }
}
var rect = new BestShape(0, 0);
console.log(rect);
console.log(Object.getPrototypeOf(rect) === BestShape.prototype);
console.log(Object.getPrototypeOf(BestShape.prototype) === Shape.prototype);
console.log(Object.getPrototypeOf(Shape.prototype) === null);
console.log(Object.getPrototypeOf(BestShape) === Shape);
console.log(Object.getPrototypeOf(Shape) === Function.prototype);

If you don't want to avoid using this in your constructor, an alternative is extending a function whose prototype is null. The downside is that your class will inherit from that function, instead of directly from Function.prototype.

function NullClass() {}
NullClass.prototype = null;

class Shape extends NullClass {
  constructor(x, y) {
    super();
    this.x = x;
    this.y = y;
  }
  move(x, y) {
    this.x += x;
    this.y += y;
  }
}
var rect = new Shape(0, 0);
console.log(rect);
console.log(Object.getPrototypeOf(rect) === Shape.prototype);
console.log(Object.getPrototypeOf(Shape.prototype) === null);
console.log(Object.getPrototypeOf(Shape) === NullClass);
console.log(Object.getPrototypeOf(NullClass) === Function.prototype);

If you don't want to reuse NullClass, you can define it inline

class Shape extends Object.assign(function(){},{prototype:null}) { /* ... */ }
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • This is interesting! I didn't know you could do this. Good point that doing things this way has the advantage of letting you still use `Function.prototype` methods on `Shape`. – Frank Tan Jul 19 '16 at 01:27
  • I read you could `extends null` but could not figure out why `super()` would not work. Thanks for explaining it. That's really wonky though... – Michael Theriot Aug 23 '16 at 03:05
  • @MichaelTheriot Yes, it doesn't work because `null` is not callable, but it's so annoying. I read they are considering fixing it. – Oriol Aug 23 '16 at 03:08
3

You will have to manually set Shape.prototype's prototype to null.

class Shape {
  constructor(x, y, width, height) {
    this.x = x,
    this.y = y,
    this.width = width,
    this.height = height;
  }
    
  move(x, y) {
    this.x += x,
    this.y += y;
  }
}

// This is the key line.
Object.setPrototypeOf(Shape.prototype, null);

const rect = new Shape(0, 0, 4, 2);
console.log(Object.getPrototypeOf(rect) === Shape.prototype);
console.log(Object.getPrototypeOf(Object.getPrototypeOf(rect)) !== Object.prototype);
Frank Tan
  • 4,234
  • 2
  • 19
  • 29