Javascript bind bug?

Viewed 59

Consider the following use case for creating two classes Point and YAxisPoint:

function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype.toString = function() { 
  return `${this.x},${this.y}`; 
};

var p = new Point(1, 2);
var YAxisPoint = Point.bind(null, 0);
var axisPoint = new YAxisPoint(5);

console.log(p.toString()); // '1,2'
console.log(axisPoint.toString()); // '0,5'

Executing this code in the browser yields the correct results. Now let's say I want to override the toString function for YAxisPoint constructor:

YAxisPoint.prototype.toString = function() { return 'override'; }
var newAxisPoint = new YAxisPoint(5);
console.log(newAxisPoint.toString()); // 'override'

Executing this code in the browser yields:

Uncaught TypeError: Cannot set property 'toString' of undefined

Question 1

Why the prototype for YAxisPoint is undefined?

I really don't get it. If I add the following polyfill it works as expected.

Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    fBound.prototype = new fNOP();

    return fBound;
  };

Question 2

Is there a bug in the browser implementations for bind? I tried this in Opera, Safari, Chrome, and Firefox and they all have the same problem.

Here is a link to a code that you can run to test.

The polyfill in this sandbox is commented out. You can uncomment that to validate.

1 Answers

The expected result can be achieved by using Reflect.construct() and setting .toString() at .__proto__ of axisPoint

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

Point.prototype.toString = function() { 
  return `${this.x},${this.y}`; 
};

var p = new Point(1, 2);
var axisPoint = Reflect.construct(Point.bind(null /* Point */, 0), [5]);

console.log(p.toString()); // '1,2'
console.log(axisPoint.toString()); // '0,5'

axisPoint.__proto__.toString = function() { 
  return 'override'; 
}

console.log(axisPoint.toString());

Related