Tuesday, November 20, 2012

JavaScript Devs: Be Careful With Comparison Operators

The results of type coercion in JavaScript can be difficult to predict, even when you know the rules. In this post, we will see how subtle differences in comparisons, can lead to significant changes in the results. Be careful when relying on 'truthyness' for logic. Whenever possible, use comparison operators to clarify your meaning. 'Falsy' values include:

0
undefined
null
empty strings
false

CAUTION - Boolean objects instantiated with new Boolean(false) are 'truthy' because they're objects, and objects are truthy! In order to test against them, you need to use the .valueOf() method. It's better in almost all cases to use true and false, instead.



Empty objects and empty arrays are truthy, including objects created with new Boolean(false).

Wrong: Don't use new Boolean()

var myBool = new Boolean(false);
test('Boolean object', function () {
  ok(!myBool, 'Should be falsy'); // Fails
  ok(!myBool.valueOf(),
    'Should be falsy.'); // Passes
});


Right: Use true or false in your boolean declarations

var myBool = false;
test('Boolean object', function () {
  ok(!myBool, '!myBool should be false.');
});


The following series of tests are meant to demonstrate how different types of comparisons in JavaScript that look similar can deliver very different results.

Always be careful that you're using the correct test for your situation:

function truthy(x) {
  if (x) {
    return true;
  } else {
    return false;
  }
}


test('Truthy', function () {
  // Falsy
  equal(truthy(0), true, 'truthy(0)'); // Fail
  equal(truthy(''), true, "truthy('')");  // Fail
  equal(truthy(null), true, 'truthy(null)'); // Fail
  equal(truthy(undefined), true,
    'truthy(undefined)'); // Fail
  equal(truthy(false), true, 'truthy(false)'); // Fail

  // Truthy
  equal(truthy('0'), true, "truthy('0')"); // Pass
  equal(truthy(new Boolean(false)), true,
    'truthy(new Boolean(false))'); // Pass
  equal(truthy({}), true, 'truthy({})'); // Pass
  equal(truthy([]), true, 'truthy([])'); // Pass
  equal(truthy([0]), true, 'truthy([0])'); // Pass
  equal(truthy([1]), true, 'truthy([1])'); // Pass
  equal(truthy(['0']), true, "truthy(['0'])"); // Pass
  equal(truthy(['1']), true, "truthy(['1'])"); // Pass
});


These are the falsy and truthy values we'll use as a baseline for a series of other comparisons.

CAUTION - Often, developers will use if (x) when they really want to see if the value has been set at all. This is especially problematic when 0, empty strings, or false are valid values. In that case, you want the test to pass as long as the expression evaluates to anything other than null or undefined. A good rule of thumb is to only use if (x) to evaluate booleans or the existence of objects or arrays.

function exists(x) {
  if (x !== undefined && x !== null) {
    return true;
  } else {
    return false;
  }
}


test('exists', function () {
  // Falsy
  equal(exists(0), true, 'exists(0)'); // Pass
  equal(exists(''), true, "exists('')"); // Pass
  equal(exists(null), true, 'exists(null)');
  equal(exists(undefined), true, 'exists(undefined)');
  equal(exists(false), true, 'exists(false)'); // Pass

  // Truthy
  equal(exists('0'), true, "exists('0')"); // Pass
  equal(exists(new Boolean(false)), true,
    'exists(new Boolean(false))'); // Pass
  equal(exists({}), true, 'exists({})'); // Pass
  equal(exists([]), true, 'exists([])'); // Pass
  equal(exists([0]), true, 'exists([0])'); // Pass
  equal(exists([1]), true, 'exists([1])'); // Pass
  equal(exists(['0']), true, "exists(['0'])"); // Pass
  equal(exists(['1']), true, "exists(['1'])"); // Pass
});


Of course, a shorter version of that function will return the same results:

function exists(x) {
  return (x !== undefined && x !== null);
}


CAUTION - The == operator can return some problematic results due to type coercion.

For example, say you're checking an array to be sure it contains a valid price, and some items cost $0.00. Your code could fail because ['0.00'] == false. Use === instead.

var isFalse = function isFalse(x) {
  return (x == false);
};


test('isFalse using ==', function () {
  // Falsy
  equal(isFalse(0), true, 'isFalse(0)'); // Pass
  equal(isFalse(''), true, "isFalse('')"); // Pass
  equal(isFalse(null), true, 'isFalse(null)'); // Fail
  equal(isFalse(undefined), true, 'isFalse(undefined)'); // Fail
  equal(isFalse(false), true, 'isFalse(false)'); // Pass


  // Truthy
  equal(isFalse('0'), true, "isFalse('0')"); // Pass
  equal(isFalse(new Boolean(false)), true,
    'isFalse(new Boolean(false))'); // Pass
  equal(isFalse({}), true, 'isFalse({})'); // Fail
  equal(isFalse([]), true, 'isFalse([])'); // Pass
  equal(isFalse([0]), true, 'isFalse([0])'); // Pass
  equal(isFalse([1]), true, 'isFalse([1])'); // Fail
  equal(isFalse(['0']), true, "isFalse(['0'])"); // Pass
  equal(isFalse(['1']), true, "isFalse(['1'])"); // Fail
});


The following code will only return true if x is actually set to false:

var isFalse = function isFalse(x) {
  return (x === false);
};


The following function is dangerous, because it will return true for 1, [1], and ['1'].

var isTrue = function isTrue(x) {
  return (x == true);
};


Use === instead to eliminate the possibility of false positives.

Remember that if (x) will always return true when x is an object -- even if the object is empty. It's common to use ducktyping when you examine objects. (If it walks like a duck and talks like a duck, treat it like a duck.) The idea is similar to using feature detection in browsers, instead of running checks against the browser string. Ducktyping is feature detection for objects.

if (typeof foo.bar === 'function') {
  foo.bar();
}



4 comments:

Unknown said...

Bottom line: if you need to perform truthy/falsy evaluation, don't rely on coercion. It's not a handy shortcut, it's a pitfall, so if you need true/false values, write your code in such a way that once you hit your conditional, you can rigorously perform === true or === false checks.

Richard Walsh said...

Yep! Well said.

websitedesignersmelbourne said...

Thanks for sharing useful information. I always make sure to bookmark pages like this because you know it will be useful in the future too. thanks again.
Website Design Perth

Website Design Portland said...

This is a Great information..thanx! Web Design Portland

Post a Comment