Before I start: Thank you so much for making this code easily readable! Thank you from the bottom of my heart!
Second thing that I have noticed was the name. It's wrong! If it is a sptrinf
-like function, give it the right name. sprintf
! Done! No overly complicated boring names.
You have an argument called string
. Please, don't do that. In fact, remove it's name! You know it is the first element.
You can do this:
var text = arguments[0];
for (var i = 1; i < arguments.length; i++) {
Done! But it won't be needed and you will see why.
You still didn't learn: Store the length in a local variable. Always! Like this:
for (var i = 1, length = args.length; i < length; i++) {
Accessing a property in an object (in this case, an array) is slower than accessing a loval variable. By setting the local variable with that slow property, we increase performance.
It is really a great idea to have literal regular expressions, but the whole code is quite... shady... Specially the useless validations!
You expect a string to be a real string so you can do something... But why? What about a number? What about an array? What around something else? Why can't I convert anything into a string? Every single object has the .toString()
method.
And why that NaN
there? What is it for? Is that to say it is NaN
?
The %o
is doing something somewhat somewhere that I don't get. By the C++ documentation and the PHP documentation specify that this is the octal representation.
And now, the questions:
- Why do you check if an argument is a
string
before converting it to string?
- Why do you check if an argument is og any type before converting?
- why don't you do any kind of validation?
- Why are you using
exec
?
I would never do it on this way. It's so frail and picky. It's so hard to change anything without breaking it. You have to deal with things you should, like breaking a string in 3 parts and replacing the middle part with something. That's a bad way to do a .replace()
.
I would do like this:
function sprintf(){
var toBase = function(value, base){
//converts to the required bases
return (parseInt(value, 10) || 0).toString(base);
};
var map = {
s: function(value){
return value.toString();
},
d: function(value){
return toBase(value, 10);
},
i: function(value){
return this.d(value);
},
b: function(value){
//binary conversion
return toBase(value, 2);
},
o: function(value){
//octal conversion
return toBase(value, 8);
},
x: function(value){
//hexadecimal conversion
return toBase(value, 16);
},
X: function(value){
//HEXADECIMAL CONVERSION IN CAPITALS
return toBase(value, 16).toUpperCase();
},
e: function(value){
//scientific notation
return (parseFloat(value, 10) || 0).toExponential();
},
E: function(value){
return this.e(value, 10).toUpperCase();
},
f: function(value){
//floating point
return (parseFloat(value, 10) || '0.0').toString();
}
};
//crude way to extract the keys
var keys = '';
for(var k in map)
{
keys += k;
}
var args = Array.prototype.slice.call(arguments).slice();
return args.shift().toString().replace(new RegExp('%([' + keys + '])','g'),function(_, type){
if(!args.length)
{
throw new Error('Too few elements');//appropriate type here?
}
return map[type](args.shift());
});
}
Come on! It's so easy to understand! You can change it's functionality easily!
If you want, you can remove that loop to fetch the keys and use this literal regular expression: /%([diobsxXeEf])/g
. Same thing, but faster and less flexible.
And this is a "good enough" clone of the sprintf
function. It doesn't support padding, float-number size specifitation and others. But, it works for the bare minimum.
Outside the scope of this review, thanks to you, I finally have a function to write my polyglot questions! Also, you may want to look at http://phpjs.org/functions/sprintf/ if you want a perfect clone of the sprintf
function.
To reflect closely the desired implementation, one can change the map
variable. I went a bit further and changed all unnecessary things. To be consistent with the MDN documentation and the Chrome console API documentation, you can do this:
function sprintf(){
var map = {
s: function(value){
return value.toString();
},
d: function(value){
return (parseInt(value, 10) || 0).toString();
},
i: function(value){
return this.d(value);
}
f: function(value){
return (parseFloat(value, 10) || '0.0').toString();
}
};
var args = Array.prototype.slice.call(arguments).slice();
return args.shift().toString().replace(/%([difs])/g,function(_, type){
if(!args.length)
{
throw new Error('Too few elements');
}
return map[type](args.shift());
});
}
The %o
, %O
and %c
specifiers are implemented internally and isn't possible to implement using this method. %c
and %C
work on elements, by creating a list in the console or a link to the element to show in the Element Inspector. The %c
uses CSS styles to style the output in the console itself.
printf
stands for "format". \$\endgroup\$