16

I was surprised to see that

/a/ === /a/

evaluates to false in JavaScript. Reading through the specs:

Two regular expression literals in a program evaluate to regular expression objects that never compare as === to each other even if the two literals' contents are identical.

Since === cannot be used to test for equality, how can equality of regular expressions be tested in JavaScript?

Randomblue
  • 112,777
  • 145
  • 353
  • 547
  • Go through this **[JavaScript === vs == : Does it matter which “equal” operator I use?](http://stackoverflow.com/questions/359494/javascript-vs-does-it-matter-which-equal-operator-i-use)** – Siva Charan May 27 '12 at 19:24
  • 5
    You're talking about JavaScript, the language in which `[] == []` evaluates to `False`. – Tyler Crompton May 27 '12 at 19:25
  • 3
    @SivaCharan how is that useful? – Matt Ball May 27 '12 at 19:25
  • And `{}+[]===[]+{}` is false. – Derek 朕會功夫 May 27 '12 at 19:25
  • @Derek but `{}+[]` is `0` and `[]+{}` is `"[object Object]"` ... – Matt Ball May 27 '12 at 19:27
  • It shouldn't matter which is on which side of `+`. But in JavaScript... strange things happen. – Derek 朕會功夫 May 27 '12 at 19:27
  • 3
    @TylerCrompton: don't forget `[] == [].length`. [*This question*](http://stackoverflow.com/questions/560263/regular-expressions-equivalence) might answer the original question, or at least nudge OP in the right direction. – DCoder May 27 '12 at 19:28
  • 1
    There's nothing special going on here - this is expected and logical behaviour. `RegExp` is not a special object, like strings, objects, and arrays - you wouldn't expect `new MyClass(x) === new MyClass(x)` to be true either. – Eric May 27 '12 at 19:32

5 Answers5

22

Here's a case that even covers ordering of flags.

function regexEqual(x, y) {
    return (x instanceof RegExp) && (y instanceof RegExp) && 
           (x.source === y.source) && (x.global === y.global) && 
           (x.ignoreCase === y.ignoreCase) && (x.multiline === y.multiline);
}

Tests:

regexEqual(/a/, /a/) // true
regexEqual(/a/gi, /a/ig) // also true.
regeXEqual(/a/, /b/) // false
Bart Kiers
  • 166,582
  • 36
  • 299
  • 288
Arka
  • 837
  • 4
  • 8
  • Isn't that the same as calling `.toString()`? – Eric May 27 '12 at 19:29
  • 2
    @Eric: No. .toString() returns the exact thing you put in. This uses a predefined order of flags to make sure that /a/gi === /a/ig. – Arka May 27 '12 at 19:31
  • 1
    Very nice. I didn't know about `.source` & co. +1 – Matt Ball May 27 '12 at 19:31
  • @JohnathonArka: Ah, hadn't thought about order of flags. – Eric May 27 '12 at 22:34
  • You haven't checked all flags, only `g`, `i` and `m`. For example, with your function, `regexEqual(/a/s, /a/)` would return true. You also need to check the `dotAll` (`s`), `unicode` (`u`) and `sticky` (`y`) properties. You can find a complete list of flags [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Parameters). – Donald Duck Mar 22 '19 at 21:39
8

Here's a function that fully tests all the relevant regex properties and makes sure it's the right type of object:

function regexSame(r1, r2) {
    if (r1 instanceof RegExp && r2 instanceof RegExp) {
        var props = ["global", "multiline", "ignoreCase", "source", "dotAll", "sticky", "unicode"];
        for (var i = 0; i < props.length; i++) {
            var prop = props[i];
            if (r1[prop] !== r2[prop]) {
                return false;
            }
        }
        return true;
    }
    return false;
}

And, since flags sometimes get added to the regex object with new features (as has happened since this original answer in 2012 - though the above code has been updated as of 2019), here's a version that is a bit more future proof on future flags being added since it compares whatever flags are there rather than looking for a specific set of flags. It sorts the flags before comparing to allow for minor differences in how the regex was specified that wouldn't not actually change functionality.

function regexSame(r1, r2) {
    return r1 instanceof RegExp && 
           r2 instanceof RegExp &&
           r1.source === r2.source &&
           r1.flags.split("").sort().join("") === r2.flags.split("").sort().join("");
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • You haven't checked all flags, only `g`, `i` and `m`. For example, with your function, `regexSame(/a/s, /a/)` would return true. You also need to check the `dotAll` (`s`), `unicode` (`u`) and `sticky` (`y`) properties. You can find a complete list of flags [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Parameters). – Donald Duck Mar 22 '19 at 21:42
  • @DonaldDuck - OK, I added those properties. I'm not sure those properties were documented (or perhaps even supported) back in 2012 when this answer was written. Anyway, it's been updated now. – jfriend00 Mar 22 '19 at 21:54
  • 1
    @DonaldDuck - I added a bit more future proof version that will continue to work when new flags are added in the future. – jfriend00 Mar 23 '19 at 00:27
  • For what it is worth as of 2019-12-06 Edge does not support the flags property in regular expressions. [Can I use RegExp Flags](https://caniuse.com/#search=regexp%20flags) The first answer may be a better idea until Edge supports them. – Dan Hooper Dec 06 '19 at 19:08
  • 1
    @DanHooper - Hopefully, that will be soon since Edge is in the process of switching over to the Chromium engine. I presume that means the Chrome JS engine too. – jfriend00 Dec 06 '19 at 19:09
2

You can check the types with typeof, then toString() both regexes and compare those. It won't cover cases with equivalent flags, such as /a/gi and /a/ig, though.

function regexEquals(a, b)
{
    if (typeof a !== 'object' || typeof b !== 'object') return false;

    return a.toString() === b.toString();
}

Unfortunately there's no more-specific type from typeof, so if you really want to make sure they're regexes (or regex-like) you could do something along these lines:

RegExp.prototype.regexEquals = function (other)
{
    return (typeof other.regexEquals === 'function')
        && (this.toString() === other.toString());
}

Then:

/a/.regexEquals(/a/); // true
/a/.regexEquals(/b/); // false
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
2

Compare them using toString(), and check their type too:

var a = /a/,
    b = /a/;

a.toString() === b.toString() && typeof(a) === typeof(b)  //true

var c = /a/,
    d = /b/;

c.toString() === d.toString() && typeof(c) === typeof(d)  //false
Derek 朕會功夫
  • 92,235
  • 44
  • 185
  • 247
0

Answers above didn't consider case-sensitivity. So built upon jfriend00's answer, the function should be

function regexEqual(a, b) {
    if (!(a instanceof RegExp) || !(b instanceof RegExp)) {
        return false;
    }
    let sourceA = a.source;
    let sourceB = b.source;
    const flagsA = a.flags.split('').sort().join(',');
    const flagsB = b.flags.split('').sort().join(',');
    if (flagsA.includes('i') && flagsB.includes('i')) {
        sourceA = sourceA.toLowerCase();
        sourceB = sourceB.toLowerCase();
    }
    return sourceA === sourceB && flagsA === flagsB;
}
Leedehai
  • 3,660
  • 3
  • 21
  • 44
  • I believe you've pointed out a real issue, but the solution is broken - consider regexps like `/\d/i` (a single digit) and `/\D/i` (a single non-digit). While `/i` doesn't make sense in these simple examples, it's clear that a regex's source can still be case-sensitive even if the input is not. – Jacob Raihle May 25 '22 at 07:47