Templating without the with statement
One of the limitations that ECMAScript 5 strict mode brings is the removal of the with statement. This is great news in general as the statement just makes code less readable.
var x = 6;
with (obj) {
// You cannot be certain that the "x" you use in this block
// is the one you want since obj.x can be present.
alert(x);
}
There is, however, one type of libraries that are built upon this statement. Can you guess what libraries I’m talking about? (Yeah, I know—the title is a big hint.)
It’s templating libraries! Almost every JavaScript templating library I have seen made a use of the with statement. And what is the issue that makes all of the libraries use the statement, you ask? Let’s say that you have an object that defines all variables specific to a template instance and you have the template string (HTML with variables for instance). In such case, the with statement is the simplest way to map the key-value storage (the object) to multiple local variables (one for each key). To demonstrate this situation, I’m going to borrow the EJS syntax.
// Here I have the object that holds template variables...
var model = {
'title': 'Lorem ipsum'
};
// ...and here is a simple EJS template...
var tpl = '<h1><%= title %></h1>';
// ...from which I get the following JS code:
var js = "'<h1>' + title + '</h1>'";
with (model) {
// I can access "model.title" as "title".
eval('return ' + js);
}
There is one exception I found and it is the ECO (Embedded CoffeeScript) library:
// We have the same template object as we had above...
var model = {
'title': 'Lorem ipsum'
};
// ...but the template looks slightly different:
var tpl = '<h1><%= @title %></h1>';
// Following the CoffeeScript syntax, this could be expressed as
var js = "'<h1>' + this.title + '</h1>'";
// By using "this" instead of a variable number of local variables,
// the template JS code can be executed without the with statement:
(function () { return eval(js); }).call(model);
// We could also create a detached anonymous function
// to execute the code in a limited scope:
var execute = new Function(js);
return execute.call(model);
I consider this a nice workaround. However, if we want to use the EJS syntax (or any syntax other than ECO with the @ prefixes), the with statement is still very useful.
So what is the solution if we need a future-proof library in which we cannot use the with statement? We just need to realize that the Function constructor takes more than one argument—one for each of the new function’s arguments.
var fn = new Function('a', 'b', 'return a + b');
fn(2, 3); // 5
It is also possible to call the call and apply methods of the constructor:
var fn = Function.apply(null, [ 'a', 'b', 'return a + b' ]);
fn(2, 3); // 5
With this knowledge, I’m going to fix the code above.
var model = {
'title': 'Lorem ipsum'
};
var tpl = '<h1><%= title %></h1>';
var js = "'<h1>' + title + '</h1>'";
// First, I get the model's keys and values:
var keys = [];
var values = [];
for (var key in model) {
if (model.hasOwnProperty(key)) {
keys.push(key);
values.push(model[key]);
}
}
// Then, I push the template JS code (function body) to the keys:
keys.push('return ' + js);
var execute = Function.apply(null, keys);
return execute.apply(null, values);
What do you think about this approach? If you know about a better way to achieve the same result, let me know! Thanks.