0

I am trying to port a Java application for use in node.js, and am running into an issue with Object inheritance. I have a base object HVal, and 2 subclasses, HBin and HBool. When I try to use both HBin and HBool, the 1st object that is loaded is overridden by the 2nd object that is loaded, even though they are being assigned to different vars. Anyone have an idea of what is going on here.

HVal.js

/** Package private constructor */
function HVal() {};

/** Abstract functions that must be defined in inheriting classes 
 * hashCode: int - Hash code is value based
 * toZinc: String - Encode value to zinc format
 * equals: boolean - Equality is value based
 */

/** String - String format is for human consumption only */
HVal.prototype.toString = function() { return this.toZinc(); };

/** int - Return sort order as negative, 0, or positive */
HVal.prototype.compareTo = function(that) { return this.toString().localeCompare(that); };

/** boolean - check for type match */
HVal.prototype.typeis = function (check, prim, obj) { return typeof(check)==prim || check instanceof obj; };

/** Add hashCode function to Javascript String object */
String.prototype.hashCode = function() {
  var hash = 0, i, chr, len;
  if (this.length == 0) return hash;
  for (i = 0, len = this.length; i < len; i++) {
    chr   = this.charCodeAt(i);
    hash  = ((hash << 5) - hash) + chr;
    hash |= 0; // Convert to 32bit integer
  }
  return hash;
};

/** Export for use in other modules */
module.exports = new HVal();

HBin.js

var hval = require('./HVal');

/** Private constructor */
function HBin(mime) { 
  /** MIME type for binary file */
  this.mime = mime; 
};
HBin.prototype = hval;

/** Construct for MIME type */
HBin.prototype.make = function(mime) {
  if (!hval.typeis(mime, 'string', String) || mime.length == 0 || mime.indexOf('/') < 0)
    throw new Error("Invalid mime val: \"" + mime + "\"");
  return new HBin(mime);
};

/** int - Hash code is based on mime field */
HBin.prototype.hashCode = function() { return mime.hashCode(); };

/** String - Encode as "Bin(<mime>)" */
HBin.prototype.toZinc = function()  {
  var s = "Bin(";
  for (var i=0; i<this.mime.length; ++i)
  {
    var c = this.mime.charAt(i);
    if (c > 127 || c == ')') throw new Error("Invalid mime, char='" + c + "'");
    s += c;
  }
  s += ")";
  return s.toString();
};

/** boolean - Equals is based on mime field */
HBin.prototype.equals = function(that) {
  if (!typeOf(that) == HBin) return false;
  return this.mime === that.mime;
};

/** Export for use in other modules */
module.exports = new HBin();

HBool.js

var hval = require('./HVal');

/** Private constructor */
function HBool(val) { 
  /** Boolean value */
  this.val = val;
};
HBool.prototype = hval;

/** Construct from boolean value */
HBool.prototype.make = function(val) {
  if (!hval.typeis(val, 'boolean', Boolean))
    throw new Error("Invalid boolean val: \"" + val + "\"");
  return new HBool(val); 
};

/** int - Hash code is same as java.lang.Boolean */
HBool.prototype.hashCode = function() { return this.val ? 1231 : 1237; };

/** String - Encode as T/F */
HBool.prototype.toZinc = function() { return this.val ? "T" : "F"; };

/** boolean - Equals is based on reference */
HBool.prototype.equals = function(that) { return this === that; };

/** String - String format is for human consumption only */
HBool.prototype.toString = function() { return this.val ? "true" : "false"; };

/** Export for use in other modules */
module.exports = new HBool();

index.js

var hbin = require('./HBin');
var hbool = require('./HBool');

console.log('BIN: ' + hbin.make("test/test").toString());
console.log();
console.log('T: ' + hbool.make(true).toString());
console.log('F: ' + hbool.make(false).toString());

Output - failing on first console.log

HBool.js:19
    throw new Error("Invalid boolean val: \"" + val + "\"");
Error: Invalid boolean val: "test/test"
    at HVal.HBool.make (HBool.js:19:11)
    at Object.<anonymous> (index.js:4:28)
    ...

1 Answer 1

1

The issue has to do with how you're exporting from a module and how you're assigning the prototype when you want to inherit and the issue is subtle. There are a couple things going on here. First off, module.exports is cached for modules, so every time you do:

var hval = require('./HVal');

you are getting the exact same HVal instantiated object back each time and you can never make a different object because you didn't export the constructor. That is a problem for all your modules. You should export the constructor function and let the user of the module actually create new instances of your object with new.

You can do that by changing:

module.exports = new HVal();

to:

module.exports = HVal;

And, then when you require() it, you just get the constructor:

var HVal = require('./HVal');
var HBool = require('./HBool');

And, then you can create an HBool object like this:

var hbool = new HBool();

This issue then seems to be messing up your inheritance when you assign something like:

HBool.prototype = hval;

The problem is entirely fixed if you export the constructors themselves and then change the above prototype assignment to use Object.create:

HBool.prototype = Object.create(HVal.prototype);

You can see a working demo here (modules removed to make the demo easier to show): http://jsfiddle.net/jfriend00/ty5wpkqm/


I also made another correction to the code. Instead of this:

if (!hval.typeis(mime, 'string', String) || mime.length == 0 || mime.indexOf('/') < 0)

I changed it to actually use the inherited methods on this object:

if (!this.typeis(mime, 'string', String) || mime.length == 0 || mime.indexOf('/') < 0)

This is the proper way to call methods on the current object (even inherited methods). Now, this happens to be a static method (it doesn't use the instance at all so you could move it off the object entirely, but since you have declared it on the object, you should refer to it as this.typeis().


I also noticed that your .equals() method is not correct. You have this:

/** boolean - Equals is based on mime field */
HBin.prototype.equals = function(that) {
  if (!typeOf(that) == HBin) return false;
  return this.mime === that.mime;
};

First off, did you create a new global function called typeOf()? The built-in mechanism in Javascript is lowercase typeof. Second off, typeof(that) will never be HBin. Objects in Javascript don't report a type like that. An object would report typeof(that) === "object". You can perhaps use instanceof:

/** boolean - Equals is based on mime field */
HBin.prototype.equals = function(that) {
  return that instanceof HBin && this.mime === that.mime;
};
Sign up to request clarification or add additional context in comments.

3 Comments

This works when all the code is combined into a single file, but not when separated out into the 4 individual ones. When separated, I receive the error: HBin.prototype = Object.create(HVal.prototype); TypeError: Object prototype may only be an Object or null: undefined
@ShawnJacobson - did you change your module.exports how I explained? It will work fine if you do them properly. Your error message is because HVal (which should come form the module exports) isn't the right value.
My bad. I was close, but forgot to move the "new" keyword, I had only removed the parenthesis. Thank you @jfriend00!!!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.