Javascript Variable Reference - javascript

Something I don't understand, which I'm sure someone with any simple knowledge of Javascript will get;
How does the 'm' variable referenced in this replace function actually refer to the input from the str - I don't understand how it takes the str as m?
str = str.replace("whatevers",function(m){ return m.toUpperCase(); })
Many thanks in advance. Tyler.

Each function defines how any functions passed in are used. The documentation for String.prototype.replace() explains how it's used in the section on specifying a function as a parameter.
Somewhere in the implementation of replace, that function you're passing in is called with several arguments. The full example is:
function replacer(match, p1, p2, p3, offset, string) {
return "replacement_text";
}

In the context of string replacing, if you pass in a function as the second parameter like the way you're doing, the first argument of that function that you pass in (in your case 'm') will be anything that matches your initial first argument (in this case "whatevers"). Once it finds a match, that gets assigns to 'm', and then it will perform the toUpperCase function on that variable 'm'.

Related

Get functions parameter names JS

I don't know how to get parameter names, for example:
function func(sep='\n') {
var args = Array.prototype.slice.call(arguments);
}
func("Arg1", "Argument2", "Something", sep="MySeparator")
// I want to get value of "sep" in arguments of function
// In this example it should set optional parameter "sep" to "MySeparator"
Anyone can help?
No, you can’t get name of arguments, if you want name/value pairs as arguments then try to pass it as an object like show below.
function func(args) {
args.arg1;
args.arg2;
}
func({ arg1: “some value”, arg2: “other value” });
I'm not sure if I understood the problem correctly, but seems like you want to implement something like this:
function func(separator = '\n', ...args) {
return args.join(separator);
}
console.log(func(' - MySeparator - ', 'Arg1', 'Arg2', 'Arg3'));
The first argument in func accepts a separator you want to use, while all other arguments will be passed as an array into the function where you can use join to glue them together with your separator.
UPD1: Why we can not accept spread argument first and then the separator at the end?
As stated in MDN:
A function's last parameter can be prefixed with ... which will cause all remaining (user supplied) arguments to be placed within a "standard" Javascript array. Only the last parameter can be a "rest parameter".
Short explanation for this case: we don't know if the developer passed the argument into separator or the rest arguments. That is why we consuming the known positional arguments to match them to their appropriate variable names and only then we can consume "everything that left".

Regex to define the number of appearances substituted [duplicate]

I'd like to know how to replace a capture group with its uppercase in JavaScript. Here's a simplified version of what I've tried so far that's not working:
> a="foobar"
'foobar'
> a.replace( /(f)/, "$1".toUpperCase() )
'foobar'
> a.replace( /(f)/, String.prototype.toUpperCase.apply("$1") )
'foobar'
Would you explain what's wrong with this code?
You can pass a function to replace.
var r = a.replace(/(f)/, function(v) { return v.toUpperCase(); });
Explanation
a.replace( /(f)/, "$1".toUpperCase())
In this example you pass a string to the replace function. Since you are using the special replace syntax ($N grabs the Nth capture) you are simply giving the same value. The toUpperCase is actually deceiving because you are only making the replace string upper case (Which is somewhat pointless because the $ and one 1 characters have no upper case so the return value will still be "$1").
a.replace( /(f)/, String.prototype.toUpperCase.apply("$1"))
Believe it or not the semantics of this expression are exactly the same.
I know I'm late to the party but here is a shorter method that is more along the lines of your initial attempts.
a.replace('f', String.call.bind(a.toUpperCase));
So where did you go wrong and what is this new voodoo?
Problem 1
As stated before, you were attempting to pass the results of a called method as the second parameter of String.prototype.replace(), when instead you ought to be passing a reference to a function
Solution 1
That's easy enough to solve. Simply removing the parameters and parentheses will give us a reference rather than executing the function.
a.replace('f', String.prototype.toUpperCase.apply)
Problem 2
If you attempt to run the code now you will get an error stating that undefined is not a function and therefore cannot be called. This is because String.prototype.toUpperCase.apply is actually a reference to Function.prototype.apply() via JavaScript's prototypical inheritance. So what we are actually doing looks more like this
a.replace('f', Function.prototype.apply)
Which is obviously not what we have intended. How does it know to run Function.prototype.apply() on String.prototype.toUpperCase()?
Solution 2
Using Function.prototype.bind() we can create a copy of Function.prototype.call with its context specifically set to String.prototype.toUpperCase. We now have the following
a.replace('f', Function.prototype.apply.bind(String.prototype.toUpperCase))
Problem 3
The last issue is that String.prototype.replace() will pass several arguments to its replacement function. However, Function.prototype.apply() expects the second parameter to be an array but instead gets either a string or number (depending on if you use capture groups or not). This would cause an invalid argument list error.
Solution 3
Luckily, we can simply substitute in Function.prototype.call() (which accepts any number of arguments, none of which have type restrictions) for Function.prototype.apply(). We have now arrived at working code!
a.replace(/f/, Function.prototype.call.bind(String.prototype.toUpperCase))
Shedding bytes!
Nobody wants to type prototype a bunch of times. Instead we'll leverage the fact that we have objects that reference the same methods via inheritance. The String constructor, being a function, inherits from Function's prototype. This means that we can substitute in String.call for Function.prototype.call (actually we can use Date.call to save even more bytes but that's less semantic).
We can also leverage our variable 'a' since it's prototype includes a reference to String.prototype.toUpperCase we can swap that out with a.toUpperCase. It is the combination of the 3 solutions above and these byte saving measures that is how we get the code at the top of this post.
Why don't we just look up the definition?
If we write:
a.replace(/(f)/, x => x.toUpperCase())
we might as well just say:
a.replace('f','F')
Worse, I suspect nobody realises that their examples have been working only because they were capturing the whole regex with parentheses. If you look at the definition, the first parameter passed to the replacer function is actually the whole matched pattern and not the pattern you captured with parentheses:
function replacer(match, p1, p2, p3, offset, string)
If you want to use the arrow function notation:
a.replace(/xxx(yyy)zzz/, (match, p1) => p1.toUpperCase()
Old post but it worth to extend #ChaosPandion answer for other use cases with more restricted RegEx. E.g. ensure the (f) or capturing group surround with a specific format /z(f)oo/:
> a="foobazfoobar"
'foobazfoobar'
> a.replace(/z(f)oo/, function($0,$1) {return $0.replace($1, $1.toUpperCase());})
'foobazFoobar'
// Improve the RegEx so `(f)` will only get replaced when it begins with a dot or new line, etc.
I just want to highlight the two parameters of function makes finding a specific format and replacing a capturing group within the format possible.
SOLUTION
a.replace(/(f)/,(m,g)=>g.toUpperCase())
for replace all grup occurrences use /(f)/g regexp. The problem in your code: String.prototype.toUpperCase.apply("$1") and "$1".toUpperCase() gives "$1" (try in console by yourself) - so it not change anything and in fact you call twice a.replace( /(f)/, "$1") (which also change nothing).
let a= "foobar";
let b= a.replace(/(f)/,(m,g)=>g.toUpperCase());
let c= a.replace(/(o)/g,(m,g)=>g.toUpperCase());
console.log("/(f)/ ", b);
console.log("/(o)/g", c);
Given a dictionary (object, in this case, a Map) of property, values, and using .bind() as described at answers
const regex = /([A-z0-9]+)/;
const dictionary = new Map([["hello", 123]]);
let str = "hello";
str = str.replace(regex, dictionary.get.bind(dictionary));
console.log(str);
Using a JavaScript plain object and with a function defined to get return matched property value of the object, or original string if no match is found
const regex = /([A-z0-9]+)/;
const dictionary = {
"hello": 123,
[Symbol("dictionary")](prop) {
return this[prop] || prop
}
};
let str = "hello";
str = str.replace(regex, dictionary[Object.getOwnPropertySymbols(dictionary)[0]].bind(dictionary));
console.log(str);
In the case of string conversion from CamelCase to bash_case (ie: for filenames), use a callback with ternary operator.
The captured group selected with a regexp () in the first (left) replace arg is sent to the second (right) arg that is a callback function.
x and y give the captured string (don't know why 2 times!) and index (the third one) gives the index of the beginning of the captured group in the reference string.
Therefor a ternary operator can be used not to place _ at first occurence.
let str = 'MyStringName';
str = str.replace(/([^a-z0-9])/g, (x,y,index) => {
return index != 0 ? '_' + x.toLowerCase() : x.toLowerCase();
});
console.log(str);

How to pass the method defined on prototype to Array.map as callback

I have an array
var arr = [' A ', ' b ', 'c'];
and I want to trim the spaces from each of the element from array.
It can be done by using Array.map as
arr.map(function(el) {
return el.trim();
});
I'm curious about passing the trim/toLowerCase function directly to the map as callback function, like arr.map(Math.max.apply.bind(Math.max, null)); to get the maximum element from each subarray or arr.map(Number); to cast each element to Number.
I've tried
arr.map(String.prototype.trim.apply);
but it is throwing error
Uncaught TypeError: Function.prototype.apply was called on undefined, which is a undefined and not a function
I expect that String.prototype.trim.apply should be called for each element in the array with the context set to the element from array(passed to apply);
I've also tried different combinations of apply, call and bind with no success.
Why the function on prototype cannot be referenced when using map
How function can be passed as parameter to map
arr.map(String.prototype.trim.call.bind(String.prototype.trim));
call uses this internally, which must point to the trim function to work properly in this case. Simply passing String.prototype.trim.call would leave call unbound to any method, resulting in the this value pointing to window instead.
It works, but when used apply instead of call it throws error,
arr.map(String.prototype.trim.apply.bind(String.prototype.trim));
The problem is that map will pass 2 arguments, the item and the index. Therefore it ends up calling something like 'String.prototype.trim.apply('test', 0) which fails since the second argument must be an array.
one more thing [' A ', ' B ',
'c'].map(String.prototype.trim.call.bind(String.prototype.toLowerCase));,
in this, I've used trim.call and passed toLowerCase as context then
why we need trim here, why trim is not called
When using call.bind the path that you chose to access the call function reference becomes irrelevant. The function that will get called is the one that is bound.
If you want to compose functions together you will need a different approach:
var call = Function.prototype.call,
trim = call.bind(String.prototype.trim),
toLowerCase = call.bind(String.prototype.toLowerCase),
trimAndLowerCase = pipelineFrom(trim, toLowerCase);
[' TeST '].map(trimAndLowerCase);
function pipelineFrom(fn1, fn2) {
return function (val) {
return fn2(fn1(val));
};
}
However at this point you're better off with:
arr.map(function (val) {
return val.trim().toLowerCase();
});
This works, it sure is long-winded though:
var t = String.prototype.trim.call.bind(String.prototype.trim);
arr.map(t);
Because it's longwinded there are blog posts and modules devoted to uncurrying, which is what you are trying to do here.
I did ask about this here once...

Working of javascript inline functions

I am not able to grasp how function(match, p1, p2) is working.
What is use of match parameter? The code breaks if I don't write match parameter.
function incrementString(input) {
if (isNaN(parseInt(input[input.length - 1]))) return input + '1';
return input.replace(/(0*)([0-9]+$)/, function(match, p1, p2) {
var up = parseInt(p2) + 1;
return up.toString().length > p2.length ? p1.slice(0, -1) + up : p1 + up;
});
}
P.S: I am new entirely using Js for development. However, I have been working on JSF and Java since past few years.
From MDN:
str.replace(regexp|substr, newSubStr|function[, flags])
In that case, we can see that two arguments are passed to replace, a regular expression literal and a function expression. So that's:
str.replace(regexp, function)
and MDN tells us what they are:
function (replacement)A function to be invoked to create the new
substring (to put in place of the substring received from parameter
1). The arguments supplied to this function are described in the "Specifying a function as a parameter" section below.
and
The arguments to the function are as follows:
etc. etc. I won't quote the entire table.
If you leave the match argument out of the parameter list, then the values assigned to p1 and p2 will be the first and second argument instead of the second and third. Those won't be the values you need.
It would be like taking this code:
function call_with_one_two_three(f) {
f(1,2,3);
}
call_with_one_two_three(function (one, two, three) {
alert(two + three);
});
And deciding that since you weren't using one you didn't need it:
function call_with_one_two_three(f) {
f(1,2,3);
}
call_with_one_two_three(function (two, three) {
alert(two + three);
});
That giving you two + three as 3.
In short: The position of arguments matters (and the name doesn't).

Should I regexp.test before I string.replace?

When I want to replace some parts of a string, should I call replace directly like this?
var r1 = /"\+((:?[\w\.]+)(:?(:?\()(:?.*?)(:?\))|$){0,1})\+"/g;
arg = arg.replace(r1, function(outer, inner){
return eval(inner);
});
Or test for a match first, and then replace if it's a hit, like this?
var r1 = /"\+((:?[\w\.]+)(:?(:?\()(:?.*?)(:?\))|$){0,1})\+"/g;
if (r1.test(arg)) {
arg = arg.replace(r1, function(outer, inner){
return eval(inner);
});
}
I guess this boils down to how the string.replace(regex, string) function works. Will it go into my callback even if there is no match, or will it then simply return arg? In that case I assume the calling replace directly is the right way to go to avoid having the regex engine match the string twice?
You don have to use test. The function in replace only executed when a match occurs.
No matches: No function (eval) call
1 match: 1 call
2 matches: 2 calls
etc.
Also, why are you using eval? eval executes the parameter, as if it's a JavaScript expression. Since you know the input format, it's likely that you're able to achieve the same behaviour without eval.

Categories