Skip to content

Instantly share code, notes, and snippets.

@therealklanni
Last active January 20, 2016 13:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save therealklanni/7d43fbfcfa67d9bcb215 to your computer and use it in GitHub Desktop.
Save therealklanni/7d43fbfcfa67d9bcb215 to your computer and use it in GitHub Desktop.
Demystifying Property Accessors

Examining the Object property accessor behavior

Consider the following scenario

function invert(color) {
  var lookup = {
    black: 'white',
    white: 'black'
  }
  
  return lookup[color];
}

console.log(invert('black')); // => 'white'

What if you want to be able to return a default value (i.e. assume black is the initial state) instead of undefined?

console.log(invert()); // => undefined

Your first impulse might be to do the following

function invert(color) {
  var lookup = {
    black: 'white',
    white: 'black'
  }
  
  return lookup[color] || 'white'; 
}

console.log(invert('white')); // => 'black'
console.log(invert()); // => 'white'

However, you can also take advantage of this "feature" of JavaScript

function invert(color) {
  var lookup = {
    black: 'white',
    white: 'black',
    'undefined': 'white'
  }
  
  return lookup[color]; 
}

console.log(invert('white')); // => 'black'
console.log(invert()); // => 'white'

Either method works just fine, but I like the latter approach because it's cleaner, in my opinion. But it also can allow you to be more flexible without needing a bunch of if..else or ternary statements. Keep reading.

Consider a less contrived example:

function toggleState(currentState) {
  var invert = {
    disabled: 'enabled',
    enabled: 'disabled',
    'undefined': 'disabled'
  }
  
  return invert[currentState];
}

If we assume we have some object that we want to be able to toggle it's state in this way, but we may not necessarily have an initial state value (enabled by default), we can do things like:

// say we get a user record from a db, and for simplicity one user looks 
// like the following
var user = {
  name: 'Batman'
};

// and we want to be able to toggle this user's ability to log in..
// so, rather than doing something like
if (user.login === 'disabled') {
  user.login = 'enabled';
} else {
  user.login = 'disabled';
}

// we could simply do this, even if `user.login` is not yet set
user.login = toggleState(user.login);

NB

There are, of course, some caveats to this. This isn't a true replacement for switch..case's default.

Consider the following:

var lookup = {
  foo: 'bar',
  bar: 'baz',
  'undefined': 'beep'
}

lookup['foo'] // === 'bar' as expected
lookup[undefined] // === 'beep' as expected
lookup[null] // === undefined
lookup['boop'] // === undefined

So you can see that it's not a "catch all", like default. But as long as you are mindful of this, and you know what values can be expected, it should still work well.

In fact, there could be some advantages to this. Say you want to be able to detect when errant values like null or any other unexpected value exists. You can simply combine the two methods, like so

function invert(color) {
  var lookup = {
    'undefined': 'white'
    white: 'black',
    black: 'white'
  };
  
  return lookup[color] || new Error('Oops, we cannot invert '+ color);
}

console.log(invert('blue')); // => [Error: Oops, we cannot invert blue]

So this makes our lookup Object easier to debug, because we immediately know that an invalid value (not simply undefined) was passed in.

In my opinion this looks a lot nicer than doing

  return color === undefined ? 'white' : lookup[color] || new Error('Oops');

OK, but what the hell is going on here?

This quirk of JavaScript is a direct result of how the property accessor (i.e. myvar[]) internal code works. This code literally does a toString conversion on whatever is between those square brackets. For example:

var beep = {
  'function Boolean() { [native code] }': 'boop'
}

console.log(beep[Boolean]) // => "boop"

Weird, huh? Weird indeed, but also kind of cool. This means if you really want to (I don't recommend it), you could have a lookup that accounted for any possible value. If you ever get to that point, there's probably better ways to do whatever you're doing!

This also explains why normal use of property accessors works for numbers, etc (think Arrays—they're really just special Objects!)

var myarr = ['beep', 'boop'];

for (a in myarr) {
  console.log(a) // => "0" then "1"
}

So what happens when you do myarr[0] then? 0 is converted to "0" and the "0" property of the Array (Object) is accessed to retrieve the value. Pretty cool.

Once you understand some of these "quirky" behaviors of JavaScript, they seem logical rather than quirky.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment