4

I've been using Javascript string.match(*regex*) function to parse the navigator.userAgent string.

FYI, According to MDN & W3schools:

"The match() method searches a string for a match against a regular expression, and returns the matches, as an Array object."

I first parse once to get the wanted strings for the base string:

var userAgent = navigator.userAgent;
var splitted = userAgent.match(/[(][^)]+[)]|\w+\/\S+/ig);

Which gives me the following Output (Which is an Array)

Mozilla/5.0
(Macintosh; Intel Mac OS X 10_10_3)
AppleWebKit/537.36
(KHTML, like Gecko)
Chrome/41.0.2272.76
Safari/537.36

Then i parse the Navigator/Version type strings in a for loop

for (var i = 0; i < splitted.length; i++ ) {
    var str = splitted[i];
    if (str[0] == '(') {
    } else {
        var name = str.match(/[^\/]+/i);
        var version = str.match(/[0-9|\.]+/i);
}

But, very surprisingly and even though I get the desired result, I get a String Object for the name and an Array Object for the version

How is it even possible ?

Here's the snippet of the code (fiddle):

var userAgent = navigator.userAgent;
var splitted = userAgent.match(/[(][^)]+[)]|\w+\/\S+/ig);
var outputDiv = document.getElementById("log");

for (var i = 0; i < splitted.length; i++ ) {
  var str = splitted[i];
  if (str[0] == '(') {
  } else {
    var name = str.match(/[^\/]+/i);
    var version = str.match(/[0-9|\.]+/i);
    outputDiv.innerHTML += name.toString() + " is a " + typeof(name) + "<br>";
    outputDiv.innerHTML += version.toString() + " is a " + typeof(version) + "<br>";
  }
};
<div id="log"></div>

--- UPDATE ---

Thanks FactoryAidan for the answer, it was a scope problem.

Conclusion: be careful when naming global variables :)

Elie Zgala
  • 145
  • 2
  • 9
  • 11
    Start using [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript) for looking at JavaScript docs. – epascarello Mar 09 '15 at 15:12
  • 4
    also there's no way a match can return a string. – Amit Joki Mar 09 '15 at 15:13
  • 1
    Offtopic cultural advice: In english, use the term "Dear" to address a *very* formal letter, and to refer to our husbands/wives. ;) Ive edited out the slightly creepy opening line. – Jamiec Mar 09 '15 at 15:15
  • http://www.w3fools.com/ – mccainz Mar 09 '15 at 15:15
  • 2
    `.match()` can only return `Array` or `null`. – Cᴏʀʏ Mar 09 '15 at 15:15
  • 1
    Without the `/g` flag, `match` returns captured groups instead of every match. (e.g. `"test".match(/te(st)/)` is `["test", "st"]`.) That’s a one-element array here, but still an array. – Ry- Mar 09 '15 at 15:16
  • @epascarello I use both, so I edited the post a bit to avoid this kind of comment :) – Elie Zgala Mar 09 '15 at 15:21
  • @Jamiec Thanks for your correction. In fact I'm french so every cultural/language advices are welcome :) – Elie Zgala Mar 09 '15 at 15:22
  • @Jamiec this might be due to cultural difference, but why I emphasised "our" is, that I thought, why would OP use the term "Dear" to refer to other's wives/husbands. He should be doing with *his* wife. Again, I ain't native English speaker. Cleaning it up. – Amit Joki Mar 09 '15 at 15:26
  • @ElieZgala: Can you please log and post the value of `str`, as well as how `version` looks like a string object?? – Bergi Mar 09 '15 at 15:28
  • If MDN is the reference, W3schools isn't really a trusted source (but it progresses) and has no relation with the W3C – Casimir et Hippolyte Mar 09 '15 at 15:30
  • On your 'UPDATE' you only changed to `my_name` on the variable assignment. But you didn't change `name` to `my_name` on the `outputDiv.innerHTML = ` lines. So this is not going to output what you think it should. – FactoryAidan Mar 09 '15 at 16:02

2 Answers2

5

Global Variable Scope

It is because you are using name as your variable. This is a global browser window variable that is inherently a string and cannot be stored as an Array

Even if you redeclare it with var name =, you are still in the global scope. And thus name (aka window.name) simply retains the last value you assign to it.

You can test this with the following on an empty page without defining any variables at all:

console.log(name===window.name) // Returns true
console.log(name,window.name)   // Returns 'Safari Safari' for my browser

Change name to something else

If you change your name variable to simply have a different name, like my_name, it stores the result of .match() as an Array.

var my_name = str.match(/[^\/]+/i);
var version = str.match(/[0-9|\.]+/i);

console.log(typeof my_name, my_name instanceof Array) // Returns object, true

Change Scope by wrapping in a function

This is your exact code wrapped inside a function and returns the correct variable types:

function getBrowserStuff(){

    var userAgent = navigator.userAgent;
    var splitted = userAgent.match(/[(][^)]+[)]|\w+\/\S+/ig);

    for (var i = 0; i < splitted.length; i++ ) {
        var str = splitted[i];
        if (str[0] == '(') {
        } else {
            var name = str.match(/[^\/]+/i);
            var version = str.match(/[0-9|\.]+/i);
            console.log('Name','Typeof '+(typeof name), 'IsArray '+(name instanceof Array),name)
            console.log('Version','Typeof '+(typeof version),'IsArray '+(version instanceof Array),version)
        }
    }

    return 'whatever'
}

getBrowserStuff()

Changing the variable name to my_name OR wrapping code like the above function returns this:

Name    Typeof object IsArray true ["Mozilla"]
Version Typeof object IsArray true ["5.0"]
Name    Typeof object IsArray true ["AppleWebKit"]
Version Typeof object IsArray true ["600.3.18"]
Name    Typeof object IsArray true ["Version"]
Version Typeof object IsArray true ["8.0.3"]
Name    Typeof object IsArray true ["Safari"]
Version Typeof object IsArray true ["600.3.18"]

Where before it returned this:

Name    Typeof string IsArray false Mozilla
Version Typeof object IsArray true  ["5.0"]
Name    Typeof string IsArray false AppleWebKit
Version Typeof object IsArray true  ["600.3.18"]
Name    Typeof string IsArray false Version
Version Typeof object IsArray true  ["8.0.3"]
Name    Typeof string IsArray false Safari
Version Typeof object IsArray true  ["600.3.18"]
FactoryAidan
  • 2,484
  • 1
  • 13
  • 13
  • 1
    No, instead of changing the variable name better simply wrap the code in a function. It's already declared with `var` anyway. – Bergi Mar 09 '15 at 15:45
  • You must not have done it right, I edited your code by changing `name` to `my_name` and get the right variable type. See my updated answer. – FactoryAidan Mar 09 '15 at 15:52
  • 1
    Interesting, +1. Specifically, the problem is that `window.name` has a setter that stores the stringified value. Since the property is configurable, this can be bypassed with `Object.defineProperty`. But better avoid global variables. – Oriol Mar 09 '15 at 15:57
0

This is not possible, or is a bug of your implementation.

According to the ECMAScript 5.1 specification, match behaves like this:

15.5.4.10 String.prototype.match (regexp)

When the match method is called with argument regexp, the following steps are taken:

  1. Call CheckObjectCoercible passing the this value as its argument.
  2. Let S be the result of calling ToString, giving it the this value as its argument.
  3. If Type(regexp) is Object and the value of the [[Class]] internal property of regexp is "RegExp", then let rx be regexp;
  4. Else, let rx be a new RegExp object created as if by the expression new RegExp(regexp) where RegExp is the standard built-in constructor with that name.
  5. Let global be the result of calling the [[Get]] internal method of rx with argument "global".
  6. Let exec be the standard built-in function RegExp.prototype.exec (see 15.10.6.2)
  7. If global is not true, then
    1. Return the result of calling the [[Call]] internal method of exec with rx as the this value and argument list containing S.
  8. Else, global is true
    1. Call the [[Put]] internal method of rx with arguments "lastIndex" and 0.
    2. Let A be a new array created as if by the expression new Array() where Array is the standard built-in constructor with that name.
    3. Let previousLastIndex be 0.
    4. Let n be 0.
    5. Let lastMatch be true.
    6. Repeat, while lastMatch is true
      1. Let result be the result of calling the [[Call]] internal method of exec with rx as the this value and argument list containing S.
      2. If result is null, then set lastMatch to false.
      3. Else, result is not null
        1. Let thisIndex be the result of calling the [[Get]] internal method of rx with argument "lastIndex".
        2. If thisIndex = previousLastIndex then
          1. Call the [[Put]] internal method of rx with arguments "lastIndex" and thisIndex+1.
          2. Set previousLastIndex to thisIndex+1.
        3. Else, set previousLastIndex to thisIndex.
        4. Let matchStr be the result of calling the [[Get]] internal method of result with argument "0".
        5. Call the [[DefineOwnProperty]] internal method of A with arguments ToString(n), the Property Descriptor {[[Value]]: matchStr, [[Writable]]: true, [[Enumerable]]: true, [[configurable]]: true}, and false.
        6. Increment n.
    7. If n = 0, then return null.
    8. Return A.

Therefore, for your global regex, the only possible returned values are null or A, which is an array.

For the non global ones, the result of calling RegExp.prototype.exec is returned. But it also returns an array or null:

Performs a regular expression match of string against the regular expression and returns an Array object containing the results of the match, or null if string did not match.

Oriol
  • 274,082
  • 63
  • 437
  • 513
  • Thanks for your answer, but I'm not sure if I understand: Is it impossible or is it explained by using a non-global regex ? Thanks – Elie Zgala Mar 09 '15 at 15:51