Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can someone please explain this regex filtering of an array

I'm filtering an array, and found a regex on here. I'm trying to understand this:

filterArray.filter(/./.test.bind(new RegExp(key, 'g')))

But I don't understand how the array tests it value against the regex or why you have to start with /./ instead of just writing the regex. And how does bind work in this case?

EDIT: Key is just a string that I want to match, "hi" or "dog" or "anything really".

like image 358
ptf Avatar asked May 31 '13 14:05

ptf


2 Answers

The .bind() method will return a function with whatever you passed as the first argument bound as the value of this.

Since you're calling .bind() from .test(), you're getting the .test() method with the this bound to new RegExp(key, 'g').

The /./ is irrelevant here. It's just short way of getting to the RegExp.prototype.test method.

The result is that you'll effectively be doing:

var regexp = new RegExp(key, 'g');

filterArray.filter(function(val) {
    return regexp.test(val);
});

You should note that this is a little bit dangerous, because a regex object with the g modifier is stateful. This means it always starts a new search where the previous one left off.

Given this filtering scenario, the g doesn't seem at all necessary, and could really only cause problems.

Here's an example of the danger of using the g here:

var re = /./.test.bind(new RegExp("foo", 'g'));
var str = "foobar";

console.log(re(str));  // true
console.log(re(str));  // false

So calling the same regex on the same string produces two different results. If we called it again, it would be true once again.


So given the use as a .filter() callback, let's say key is "foo", then lets say one val is "foobar". It will be allowed through the filter.

But let's say the very next val is "foobaz". The search will resume on the fourth character instead of starting from the first, so the "foo" will not be found.


Here's concrete example that shows the issue in action:

DEMO: http://jsfiddle.net/s9PzL/

var filterArray = [
    "foobar",
    "foobaz",
    "foobuz",
    "foobix"
];

var result = filterArray.filter(/./.test.bind(new RegExp("foo", 'g')));

All the strings have "foo", so they should all get through. But the result shows that doesn't happen.

document.body.textContent = result.join(", "); // foobar, foobuz
like image 126
3 revsuser2437417 Avatar answered Sep 19 '22 17:09

3 revsuser2437417


I think the reason this is done this way is because you want to execute the test method on each item in the array. Just passing the test method to filter will (presumably) mess up the binding of the method.

That's why in your example:

/./

Generates an empty regex

/./.test

is the test method on that regex

/./.test.bind(new Regex(..))

binds that method to the requested method, and returns a new method that executes test where this is your supplied regex based on key.

It seems that it could have been written a lot more clear:

RegExp.prototype.test.bind(new RegExp(key, 'g'))
like image 22
Kamiel Wanrooij Avatar answered Sep 18 '22 17:09

Kamiel Wanrooij