Skip to content

Instantly share code, notes, and snippets.

@Jeff-Russ
Last active June 6, 2016 10:05
Show Gist options
  • Save Jeff-Russ/b3d7efe6ee1150813d4c87dab14f3b7b to your computer and use it in GitHub Desktop.
Save Jeff-Russ/b3d7efe6ee1150813d4c87dab14f3b7b to your computer and use it in GitHub Desktop.

JS Objects vs Ruby Hashes

Ruby has iterators baked right in for all of it's containers:

whatever.each do |variable|
  # code
end

You can do this with Ruby arrays, hashes, objects, etc but in Javascript you don't have this most of the time. You have it for arrays but when you want something like an associative array your go-to is a Javascript object literal you pretend is an associate array by just not putting methods in:

var keysnvals = {
  key1: "val1",
  key2: "val2",
  key3: "val3",
  key4: "val4"
};

Javascript has a .forEach method for arrays but it omitted from objects because they can and often do contain non-iterable properties. You don't often need to iterate over a list of method names. Remember they're OBJECTS, not just containers.

This means you find yourself typing this a lot.

for (var key in object) {
  if (object.hasOwnProperty(key)) {
    // code
  }
}

which isn't so bad but it would make sense to make a reusable function. Since you really need to insert things in the middle of the function with each different task, you'll need to make it accept a function as an argument.

var forEach = function(object, func) {
  for (var key in object) {
    if (object.hasOwnProperty(key)) {
      func(key);
    }
  }
};

Great. Now you'll never have to type it all out again. This inserted nameless function is pretty much what the do block was in Ruby. Now it looks like this in action:

forEach(someOb, function(key){
  someOb[key] = someOb[key] + "<br>";
});

We essentially have the same functionality as the Ruby .each do ... end even though we have a function rather than a method. What I mean is that if we had a fully Ruby-style hash in Javascript it would be called like this:

someOb.forEach( function(key){...});
});

not like this:

forEach( someOb, function(key){...});

Having the method belonging to our associative array-like object would have certain benefits, but that would mean making all object have .forEach and shoving things into the global scope is not something other Javascript developers like to see. We can apply it as needed.

What I do is have a collection of methods than I can pluck of out an object called orphans and "attach" to objects as needed. They only make sense when they become methods on objects, not as standalone function because they have the this keyword in them.

/* OLD ONE. 
var forEach = function(object, func) {
  for (var key in object) {
    if (object.hasOwnProperty(key)) {
      func(key);
    }
  }
};*/
orphans.forEach = function(func) {
  for (var key in this) {
    if (this.hasOwnProperty(key) && key !== "forEach") {
      func.call(this, key);
    }
  }
};

We filter out "forEach" itself from being one of the properties iterated over. After all, we are trying to pretend this is just a plain old data container with no methods!

It looks like our inner function takes two arguments but it's actually still one. You can think of the .call as gobbling up the this. Here is how you can "attach" this function as a method to an object:

var hash_one = {
  key1: "val1",
  key2: "val2",
  key3: "val3",
  key4: "val4",
  forEach: orphans.forEach // attaching it here
};
// or like this: hash_one.forEach = orphans.forEach;

// now let's use it:

hash_one.forEach(function(key){
  console.log(this[key])
});

Having this available inside the inner function isn't really needed in the above example as we could have just stated hash_one explicitly but there is a good reason to have it available if we have a bunch of reusable functions saved with names, ready to insert in forEach and be applied to many different objects. The this will take the identity of whatever object we are working on. It's nice to have them be usable in many contexts.

function prependTabs(key) {
  this[key] = "\t"+this[key];
}
function printVals(key) {
  console.log(this[key]);
}
// let's add another "hash" to prove it works on both.

var hash_two = {
  key1: "val1",
  key2: "val2",
  key3: "val3",
  key4: "val4",
  forEach: orphans.forEach
}

Here it is in action:

hash_one.forEach(prependTabs);
hash_two.forEach(prependTabs);
hash_one.forEach(printVals);
hash_one.forEach(printVals);

Now we're really starting to reap the benefits of defining a context-aware forEach method. Those last four lines of code normally look like this:

for (var key in hash_one) {
  if (hash_one.hasOwnProperty(key)) {
    hash_one[key] = "\t"+hash_one[key];
  }
}
for (var key in hash_two) {
  if (hash_two.hasOwnProperty(key)) {
    hash_two[key] = "\t"+hash_two[key];
  }
}
for (var key in hash_one) {
  if (hash_one.hasOwnProperty(key)) {
    console.log(hash_one[key]
  }
}
for (var key in hash_two) {
  if (hash_two.hasOwnProperty(key)) {
    console.log(hash_two[key]
  }
}

That's whole lot of redundant code!

Final Thoughts

We could create a "Hash" constructor and have all objects born from it have .forEach right away:

function Hash() {
  this.forEach = function(func) {
    for (var key in this) {
      if (this.hasOwnProperty(key) && key !== "forEach") {
        func.call(this, key);
      }
    }
  };
}

This doesn't really help us when we want to iterate objects not created by this constructor which brings us back to where we were before. It's not like we can force every developer in the world to always use this constructor! Also, for plain old data ("POD") objects, most people prefer object literal notation where you can add a bunch of key/value pair all together at once. We can't do that once the object is constructed.

We could me a more complex constructor that takes an object literal as an argument as well as add some other nice methods like this:

function Hash(obj_lit) {
  for (var key in obj_lit) {
    if (obj_lit.hasOwnProperty(key)) this[key] = obj_lit[key];
  }
  this.forEach = function(func) {
    for (var key in this) {
      if (this.hasOwnProperty(key) 
        && key !== "forEach"
        && key !== "printVals"
        && key !== "printKeys") {
        func.call(this, key);
      }
    }
  };
  this.printVals = function() {
    this.forEach(function(key){
      console.log(this[key]);
    });
  };
  this.printKeys = function() {
    this.forEach(function(key){
      console.log(key);
    });
  }
}

Pack that code away in a library an you have a pretty workable "Hash" class. Here is is in action:

var hash_one = new Hash({
  key1: "val1",
  key2: "val2",
  key3: "val3",
  key4: "val4",
});

hash_one.printVals();
hash_one.printKeys();

Not too bad!

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