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();
}



8 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.

Unknown said...

Yep! Well said.

Anonymous 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

swapna sri said...

Thank You! For sharing such a great article, It’s been a amazing article.
It’s provide lot’s of information, I really enjoyed to read this,
i hope, i will get these kinds of information on a regular basis from your side.
apps development company in bangalore
Cost of app like zomato
makemytrip mobile app cost in Bangalore
Blockchain application development in Bangalore

safracatz said...

Hi
Thanks for sharing such kind information about java script
Great article
App Development Companies in Kenya

swapna sri said...

Thank You! For sharing such a great article, It’s been an amazing article.It provides lot’s of information, I really enjoyed to read this, I hope, I will get these kinds of information on a regular basis from your side.
apps development company in bangalore
mobile application development companies in India
Blockchain application development in Bangalore
React native apps development companies

Naaz Aniqua said...

I am having such a good time on this wonderful post and I think it is good to have this post here. Thanks for sharing this wonderful post here.
app development companies in Indianapolis

iphone apps development companies Indianapolis

Blockchain Development Company in Indianapolis

iot companies Indianapolis

AI Company in Indianapolis

Post a Comment