Bruno Finger
Bruno Finger

Reputation: 2603

`this` reference gets lost in prototype chain

This is quite extensive, but fun and detailed.

I have defined two "classes" as follow, using the standard JavaScript ways of prototypes:

Source

function Source() {
  this._sourceGuid = "";
  this._periodGuid = "";
  this._eventName = "";
  this._eventKind = 0;

  if (arguments.length > 0) {
    if (arguments[0].__proto__ === this.__proto__) {
      this.sourceGuid(arguments[0].sourceGuid());
      this.periodGuid(arguments[0].periodGuid());
      this.eventName(arguments[0].eventName());
      this.eventKind(arguments[0].eventKind());
    } else {
      this.sourceGuid(arguments[0].sourceGuid);
      this.periodGuid(arguments[0].periodGuid);
      this.eventName(arguments[0].eventName);
      this.eventKind(arguments[0].eventKind);
    }
  }
};

Source.prototype.sourceGuid = function (value) {
  if (arguments.length > 0) {
    this._sourceGuid = value
  } else {
    return this._sourceGuid;
  }
};

Source.prototype.periodGuid = function (value) {
  if (arguments.length > 0) {
    this._periodGuid = value
  } else {
    return this._periodGuid;
  }
};

Source.prototype.eventName = function (value) {
  if (arguments.length > 0) {
    this._eventName = value
  } else {
    return this._eventName;
  }
};

Source.prototype.eventKind = function (value) {
  if (arguments.length > 0) {
    this._eventKind = value
  } else {
    return this._eventKind;
  }
};

Source.prototype.toJSON = function() {
  return {
    sourceGuid: this.sourceGuid(),
    periodGuid: this.periodGuid(),
    eventName: this.eventName(),
    eventKind: this._eventKind()
  };
};

AnalogCamerasSource extends Source

function AnalogCamerasSource() {

  this.__proto__.apply(this, arguments);

  this._serverGuid = "";
  this._cameraId = 0;
  this._waitingTime = 0;

  if (arguments.length > 0) {
    if (arguments[0].__proto__ === this.__proto__) {
      this.serverGuid(arguments[0].serverGuid());
      this.cameraId(arguments[0].cameraId());
      this.waitingTime(arguments[0].waitingTime());
    } else {
      this.serverGuid(arguments[0].serverGuid);
      this.cameraId(arguments[0].cameraId);
      this.waitingTime(arguments[0].waitingTime);
    }
  }
};

AnalogCamerasSource.prototype = Source;

AnalogCamerasSource.prototype.serverGuid = function (value) {
  if (arguments.length > 0) {
    this._serverGuid = value
  } else {
    return this._serverGuid;
  }
};

AnalogCamerasSource.prototype.cameraId = function (value) {
  if (arguments.length > 0) {
    this._cameraId = value
  } else {
    return this._cameraId;
  }
};

AnalogCamerasSource.prototype.waitingTime = function (value) {
  if (arguments.length > 0) {
    this._waitingTime = value
  } else {
    return this._waitingTime;
  }
};

AnalogCamerasSource.prototype.toJSON = function() {
  var json = this.__proto__.toJSON();

  json.serverGuid = this.serverGuid();
  json.cameraId = this.cameraId();
  json.waitingTime = this.waitingTime();

  return json;
};

Now I need an instance of AnalogCamerasSource, and I am trying to create it as simply as that:

var data = {"sourceGuid":"{05A00E05-F30D-497D-A272-156F135E1486}","periodGuid":"{8A071454-B473-4937-9C54-4899F866D7FA}","eventName":"signal-lost","eventKind":3,"serverGuid":"{9976B57D-486B-4BCA-8432-78D7C8EDB52B}","cameraId":4,"waitingTime":5};

var c = new AnalogCamerasSource(data);

Now, the line this.__proto__.apply(this, arguments); is responsible for calling the parent's constructor, and it seems it should do it fine, but at the moment it should call a parent's function in the parent's constructor, the following error is thrown:

TypeError: this.sourceGuid is not a function
    at Function.Source (http://localhost:8081/js/app/events/model/0-Source.js:14:12)
    at AnalogCamerasSource (http://localhost:8081/js/app/events/model/AnalogCamerasSource.js:3:33)

Now, here's a picture from Chrome's debugger showing the properties are there but way down the prototype chain, under this.__proto__.prototype when called like this.__proto__.apply(this, arguments) from AnalogCamerasSource.

enter image description here

So, why this.sourceGuid is not a function? How should I call the parent's constructor to have this chain working correctly?

Here's a fiddle: https://jsfiddle.net/98bp8pvh/

Upvotes: 0

Views: 73

Answers (4)

Bruno Finger
Bruno Finger

Reputation: 2603

I solved my problem with this workaround:

I changed the prototype definition:

AnalogCamerasSource.prototype = new Source();

Then I added the following function to AnalogCamerasSource:

AnalogCamerasSource.prototype.super = function() {
  Source.apply(this, arguments);
};

And then in the constructor I am calling it as:

function AnalogCamerasSource() {
  this.super.apply(this, arguments);
  ...
};

Upvotes: 0

ajai Jothi
ajai Jothi

Reputation: 2294

Refactored your code (avoid using __proto__ in your code)

function Source() {
  var args = arguments[0] || {};
  this._sourceGuid = args.sourceGuid || '';
  this._periodGuid = args.periodGuid || '';
  this._eventName = args.eventName || '';
  this._eventKind = args.eventKind || 0;
};

Source.prototype.sourceGuid = function(value) {
  if (arguments.length > 0) {
    this._sourceGuid = value || '';
  } else {
    return this._sourceGuid;
  }
};

Source.prototype.periodGuid = function(value) {
  if (arguments.length > 0) {
    this._periodGuid = value || '';
  } else {
    return this._periodGuid;
  }
};

Source.prototype.eventName = function(value) {
  if (arguments.length > 0) {
    this._eventName = value || '';
  } else {
    return this._eventName;
  }
};

Source.prototype.eventKind = function(value) {
  if (arguments.length > 0) {
    this._eventKind = value || 0;
  } else {
    return this._eventKind;
  }
};

Source.prototype.toJSON = function() {
  return {
    sourceGuid: this.sourceGuid(),
    periodGuid: this.periodGuid(),
    eventName: this.eventName(),
    eventKind: this.eventKind()
  };
};

function AnalogCamerasSource() {
  var args = arguments[0] || {};
  this._serverGuid = args.serverGuid || '';
  this._cameraId = args.cameraId || 0;
  this._waitingTime = args.waitingTime || 0;
  
  this.sourceGuid(args.sourceGuid);
  this.periodGuid(args.periodGuid);
  this.eventName(args.eventName);
  this.eventKind(args.eventKind);
};

AnalogCamerasSource.prototype = Object.create(Source.prototype);

AnalogCamerasSource.prototype.serverGuid = function(value) {
  if (arguments.length > 0) {
    this._serverGuid = value || '';
  } else {
    return this._serverGuid;
  }
};

AnalogCamerasSource.prototype.cameraId = function(value) {
  if (arguments.length > 0) {
    this._cameraId = value || 0;
  } else {
    return this._cameraId;
  }
};

AnalogCamerasSource.prototype.waitingTime = function(value) {
  if (arguments.length > 0) {
    this._waitingTime = value || 0;
  } else {
    return this._waitingTime;
  }
};

AnalogCamerasSource.prototype.toJSON = function() {
  var json = Source.prototype.toJSON.call(this);

  json.serverGuid = this.serverGuid();
  json.cameraId = this.cameraId();
  json.waitingTime = this.waitingTime();

  return json;
};

var data = {
  "sourceGuid": "{05A00E05-F30D-497D-A272-156F135E1486}",
  "periodGuid": "{8A071454-B473-4937-9C54-4899F866D7FA}",
  "eventName": "signal-lost",
  "eventKind": 3,
  "serverGuid": "{9976B57D-486B-4BCA-8432-78D7C8EDB52B}",
  "cameraId": 4,
  "waitingTime": 5
};

var c = new AnalogCamerasSource(data);
console.log(c);

Upvotes: 1

cjg
cjg

Reputation: 2684

Try changing AnalogCamerasSource.prototype = Source; to AnalogCamerasSource.prototype = new Source; and this.__proto__.apply(this, arguments); to Source.apply(this, arguments);.

The problem is that with your approach AnalogCameraSource.prototype gets set to a function (constructor for Source object), rather than to an object. This function itself does not have the methods defined on its prototype. If you set AnalogCameraSource.prototype to a new Source object (created with new Source), then AnalogCameraSource.prototype is an object, which is how things ought to be. This puts all methods defined on the Source prototype in the prototype chain of AnalogCameraSource, and the Source constructor is able to run without a hitch.

Upvotes: 0

Adam Shone
Adam Shone

Reputation: 393

Keep it simple, do your inheritance like this:

AnalogCamerasSource.prototype = Source.prototype;

...and just apply the constructor function of the parent class in the constructor of the subclass:

function AnalogCamerasSource() {
  Source.apply(this, arguments);
  ...
}

There usually isn't any need to reference __proto__ directly.

Upvotes: 0

Related Questions