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!
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!