1

Just read this highly-related question, and then reflected on what I was using in my own code base for a project at work.

I wrote a simple function to demonstrate this question (using Google Apps Script, thus the calls to Logger.log()):

function emptyValueTest() {

  var object = {
    prop1: "real_data",
    prop2: "",
    prop3: undefined,
    prop4: null
  }

  // What I have used in my code personally. All evaluate to TRUE
  if (object.prop2 === "") Logger.log("prop2 was empty");
  if (typeof object.prop3 === "undefined") Logger.log("prop3 was undefined");
  if (object.prop4 === null) Logger.log("prop4 was null");

  // The other solution I've seen. All evaluate to FALSE
  if (object.prop2) Logger.log("prop2 was empty");
  if (object.prop3) Logger.log("prop3 was undefined");
  if (object.prop4) Logger.log("prop4 was null");
}

I've been building a system for the past few months with thousands of lines of code, and I often find myself back-tracking when I see a particular value, to remember what it's previous state was before a particular conditional expression.

Since I'm the one writing the code it isn't too difficult for me to figure it out, because I know the structure of the data, the values that are supposed to be there, etc.

However, this system will be taken over by another developer after I graduate from my university, so I wonder what the best practice is for representing that a property has no data.

For example, my use case is that I have event Objects which are initialized with empty data and, during the lifetime of a semester, are eventually filled with real data. Personally I use undefined because it makes more sense to me to read if (typeof data === "undefined"), having a similar semantic meaning to "if this data is undefined" (i.e. no value yet).

But I wonder, in industry, in libraries, and in personal projects, what are commonly-used methods to make code more readable, understandable? Though the length of the first three conditionals is longer than the latter three, I find the latter three to be ambiguous.

For example, prop2 could have a non-empty string like "data", or the value true, which would cause the latter conditional if (object.prop2) to evaluate to true in both cases, because everything in JavaScript is truthy except the following:

  • null
  • undefined
  • NaN
  • empty string ("")
  • 0
  • false

And it is entirely possible that data could have "", 0, and false as valid values, whereas null, undefined and (especially) NaN are probably not typical values for real data.

Obviously I we know the structure of the data then we might impose more strict checks to disambiguate non-empty strings from true. But in a project with thousands of lines of code it seems to me that this gets out of hand fast, especially if the structure of the data changes over time.

So my question: Is there a preferred method of representing empty values in Javascript? And is there a particular reason for the preference (performance, readability, scalability, etc.)?

Community
  • 1
  • 1
Chris Cirefice
  • 5,475
  • 7
  • 45
  • 75
  • 1
    Probably this is a matter of opinion, but personally I'd use `null`, to differentiate easily between properties that have not been defined, compared to ones that have yet to been used or set. `NaN` could result from some screwed up maths that fails, and it'd be much harder to tell where it's coming from (because it'd be treated as empty). Also, an added bonus is that `null` can only result when you either assign a variable to the property that is null already, or you directly assign null. You can't get null any other way (except probably through `eval('null')`, but it's so unlikely). – Qantas 94 Heavy Dec 16 '13 at 00:06
  • I agree, it might be a matter of opinion - for example, I already expressed my own! However, I think that there are other things that should come into consideration here. For example, some operations return null values if they failed/data couldn't be found: `var split = "some,".split(", ")`; the value at `split[1]` would be `null`, which, depending on the system might be a valid value. That's why I lean toward `undefined`, because semantically it makes more sense, and there's less chance of random collision like in the example I just gave. I wonder if that's the consensus though... – Chris Cirefice Dec 16 '13 at 00:11
  • @ChrisCirefice Actually, `split[1] === undefined`. Anway, I'd use `null` too. `undefined` is used for *properties* that are not defined. But those properties are indeed defined... with no value. Then you should use `null`, since it's its precise meaning. – MaxArt Dec 16 '13 at 00:16
  • @ChrisCirefice: your point is valid though, `match()` and some DOM methods do return null, which I forgot about. But the other points still stand. – Qantas 94 Heavy Dec 16 '13 at 00:22
  • Whoops, my bad - I was thinking of a different case where I was weirdly `split()`ing data and it came up `null`. But I suppose I'll just have to work around those cases where `null` could actually be returned, I don't think it will be a large obstacle. – Chris Cirefice Dec 16 '13 at 00:27
  • 1
    I think you are over thinking the problem. You're trying to infer state from a particular value, but with `undefined` you don't know whether that's because it hasn't been assigned a value or has been assigned the `undefined` value. Just set values to whatever suits and deal with them when you read them. Don't make state an issue. – RobG Dec 16 '13 at 00:33

1 Answers1

1

Javascript is especially insidious in the NaN constant:

alert(NaN==NaN?"isNaN":"isNotNaN"); // isNotNaN !

The most common way to represent empty object is the null constant. However, you can run into troubles using it:

var coords = null;
if(coords===null) alert("empty object");
if(coords.x==0 && coords.y==0) alert("[0,0]");
// Uncaught TypeError: Cannot read property 'x' of null

There is a design pattern called Null object: to create an object representing null value with correct properties:

var nullcoords = {x:-1,y:-1}; // null object
var coords = nullcoords;
if(coords===nullcoords) alert("empty coords");
if(coords.x==0 && coords.y==0) alert("[0,0]");
// no TypeError

As RobG points in the comment, you can afford this attitude iff

  1. the nullobject default properties are not valid values
  2. the default values don't spoil you operations

However, null is not a good idea for coords properties since it is not a numeric type and can mess some arithmetic operations (not in JS, though). The below code is more appropriate (and more sophisticated) solution with custom null object handler:

function nullCoordsHandler(obj) {
  // any error handling you want
  alert("setting default values...");
  obj.x = obj.y = 0;
}

function coords() {
  var _x, _y;
  var defined = false;
  Object.defineProperty(this,"x",{
    enumerable: true,
    get:function() { if(!defined) nullCoordsHandler(this); return _x; },
    set:function(value) { defined = true; _x = value; }
  });
  Object.defineProperty(this,"y",{
    enumerable: true,
    get:function() { if(!defined) nullCoordsHandler(this); return _y; },
    set:function(value) { defined = true; _y = value; }
  });
};

var c = new coords();
if(c.x==0 && c.y==0) alert("[0,0]");
// nullCoordsHandler was called to handle the null object situation
Jan Turoň
  • 31,451
  • 23
  • 125
  • 169
  • Interesting... I didn't know about this kind of design pattern, and in my case this is exactly the kind of methodology I need. Looks like I'll have to rework some things, but it looks like this will clear up a lot of ridiculous type-checking I was implementing. Although I should add - the *object* wasn't meant to be `null`, but only *some properties* of the object. Though I think that I can still incorporate this pattern into my existing code. – Chris Cirefice Dec 16 '13 at 00:25
  • 1
    If `coords === null` why whould you then try to read `coords.x`? A more useful "null object" would be `{x:null, y:null}` since `-1` is likely a valid value. And testing `coords.x === null` is not much different to `coords.x === undefined` or `typeof coords.x == 'undefined'`. – RobG Dec 16 '13 at 00:38
  • @RobG: to avoid testing `if(coords === null)` before any operation. To set `null` instead of `-1` is probably OK in JS, but I wouldn't recommend this attitude in general, so I added the handler part instead. – Jan Turoň Dec 16 '13 at 01:14
  • @JanTuroň—so `if (coords)...` will do the trick. These types of issues are usually simple to design around, type checking in javascript isn't particularly useful. Getters and setters are also an option to avoid lots of checking. – RobG Dec 16 '13 at 03:01