In a previous post, you read about how to ensure that a deeply nested object structure exists. We wrote a simple function ensure_struct
that takes a string, splits it up, and turns it into a nested object.
In the original function it accepted 2 arguments: the string and an optional scope.
We used the optional scope as a way to aid in recursion. The function grabbed the first part of our string and made sure there was an object with that name, then it took the rest of the string and passed that object in as the scope. Down it went through the object structure until there were no more parts of the string left.
The string flamethrower.fuel.fire
became the object {flamethrower:{fuel:{fire:{}}}}
.
JS is powerful and leaning on the use of the this
keyword instead of passing an optional argument makes the function even simpler. Let's look at the final function:
var ensure_struct = function(attributes) {
if (attributes == '')
return this;
var parts = attributes.split('.');
var first_part = parts.shift();
if (typeof this[first_part] === 'undefined')
this[first_part] = {};
ensure_struct.call(this[first_part], parts.join('.'));
return this;
}
This is mostly the same as last time, but what's different is that we're not checking for a value for scope
, the optional argument. Last time we had var scope = scope || this;
to check if there was a scope given, or just set it to this.
But we can ignore that entirely because the value of this
can be set by using either of 2 core functions in JS.
Instead of calling the function and passing the scope, we can set the value of this
by using either call
or apply
.
It's here where that magic happens:
ensure_struct.call(this[first_part], parts.join('.'));
That line takes the function and uses the call
function on it. By using call
we can pass an object that we want to be the value of this
when the function is run. It's the same as just running the function ensure_struct(parts.join('.');
but telling it to use a specific value for this
(which is the last object created in our function).
While we're still recursively applying the function to our objects and strings, we no longer need to track an additional variable.
We can do the same thing with apply
but the additional arguments we send must be in an array:
ensure_struct.apply(this[first_part], [parts.join('.')]);
Now we can either call our function and set a nested object on our window (what the value of this
would be originally) or we can tell the function to apply a specific value to this
.
var ensure_struct = function(attributes) {
if (attributes == '')
return this;
var parts = attributes.split('.');
var first_part = parts.shift();
if (typeof this[first_part] === 'undefined')
this[first_part] = {};
ensure_struct.call(this[first_part], parts.join('.'));
return this;
}
var some_object = {};
ensure_struct.call(some_object, 'deeply.nested.structure');