JavaScript中用于字符串匹配的Switch语句

228

如何为以下条件编写一个 switch 语句?

如果 URL 包含“foo”,那么 settings.base_url 就是“bar”。

以下代码实现了所需的效果,但我觉得用 switch 更易于管理:

var doc_location = document.location.href;
var url_strip = new RegExp("http:\/\/.*\/");
var base_url = url_strip.exec(doc_location)
var base_url_string = base_url[0];

//BASE URL CASES

// LOCAL
if (base_url_string.indexOf('xxx.local') > -1) {
    settings = {
        "base_url" : "http://xxx.local/"
    };
}

// DEV
if (base_url_string.indexOf('xxx.dev.yyy.com') > -1) {
    settings = {
        "base_url" : "http://xxx.dev.yyy.com/xxx/"
    };
}
10个回答

400
如果您对顶部的正则表达式已经滤除了不需要比较的内容感到满意,那么您就不需要子字符串匹配,可以这样做:
switch (base_url_string) {
    case "xxx.local":
        // Blah
        break;
    case "xxx.dev.yyy.com":
        // Blah
        break;
}

...但是,这仅适用于您匹配的是完整字符串。如果base_url_string是"yyy.xxx.local",则会失败,而您当前的代码将在"xxx.local"分支中匹配。

否则,虽然您可以使用switch进行子字符串匹配,但我不建议在大多数情况下使用(更多内容请参见下文)。以下是它的样子:

function test(str) {
    switch (true) {
        case /xyz/.test(str):
            console.log("• Matched 'xyz' test");
            break;
        case /test/.test(str):
            console.log("• Matched 'test' test");
            break;
        case /ing/.test(str):
            console.log("• Matched 'ing' test");
            break;
        default:
            console.log("• Didn't match any test");
            break;
    }
}

function test(str) {
    console.log("Testing '" + str + "':");
    switch (true) {
        case /xyz/.test(str):
            console.log("• Matched 'xyz' test");
            break;
        case /test/.test(str):
            console.log("• Matched 'test' test");
            break;
        case /ing/.test(str):
            console.log("• Matched 'ing' test");
            break;
        default:
            console.log("• Didn't match any test");
            break;
    }
}
  
test("testing");
test("xyz123");
test("foo");
test("fooing");
.as-console-wrapper {
    max-height: 100% !important;
}

那是因为JavaScript中switch语句的工作方式,特别是两个关键方面:首先,case在源文本顺序下考虑,其次,选择器表达式(关键字case后面的位)是表达式,在评估该案例时进行评估(不像其他某些语言中常量)。因此,由于我们的测试表达式为true,导致结果为true的第一个case表达式将被使用。
我之所以不建议在大多数情况下使用它,是因为它既繁琐又有些令人惊讶(对于以后阅读它的人来说),与等效的if/else if/else相比。
function test(str) {
    if (/xyz/.test(str)) {
        console.log("• Matched 'xyz' test");
    } else if (/test/.test(str)) {
        console.log("• Matched 'test' test");
    } else if (/ing/.test(str)) {
        console.log("• Matched 'ing' test");
    } else {
        console.log("• Didn't match any test");
    }
}

实时示例:

function test(str) {
    console.log("Testing '" + str + "':");
    if (/xyz/.test(str)) {
        console.log("• Matched 'xyz' test");
    } else if (/test/.test(str)) {
        console.log("• Matched 'test' test");
    } else if (/ing/.test(str)) {
        console.log("• Matched 'ing' test");
    } else {
        console.log("• Didn't match any test");
    }
}
  
test("testing");
test("xyz123");
test("foo");
test("fooing");
.as-console-wrapper {
    max-height: 100% !important;
}

在这两种情况下,代码按照相同的顺序执行相同的操作,但除非你精通JavaScript的奥秘,否则后者更清晰易懂(甚至可以认为是即使你精通JavaScript时也是如此)。

1
但在大多数情况下,我不建议这样做。为什么不呢? - Zebiano
1
@Zebiano - 谢谢你问我这个问题,我很惊讶自己在最后一段没有解释清楚。我已经添加了一个解释(并且通常更新了答案)。祝编码愉快! - T.J. Crowder

113

RegExp 可以与 match 方法一起用于输入字符串。

为了确保在 case 子句中有匹配项,我们将测试原始的 str 值(提供给 switch 语句)与成功 matchinput 属性相匹配。

input 是正则表达式的静态属性,它包含原始输入字符串。

match 失败时,它返回 null。为了避免异常错误,在访问 input 属性之前,我们使用可选链操作符(或传统 ES 中的逻辑 || 条件运算符)。

const str = 'XYZ test';

switch (str) {
  case str.match(/^xyz/)?.input:
    console.log("Matched a string that starts with 'xyz'");
    break;
  case str.match(/test/)?.input:
    console.log("Matched the 'test' substring");        
    break;
  default:
    console.log("Didn't match");
    break;
}

另一种方法是使用String()构造函数将结果数组转换为字符串。该数组必须只有一个元素(没有捕获组),并且必须用量词(.*)捕获整个字符串。如果失败,null对象将变成一个'null'字符串。这可能看起来不太方便。
const str = 'XYZ test';

switch (str.toLowerCase()) {
  case String(str.match(/^xyz.*/i)):
    console.log("Matched a string without case sensitivity");
    break;
  case String(str.match(/.*tes.*/)):
    console.log("Matched a string using a substring 'tes'");
    break;
}

无论如何,更优雅的解决方案是使用test方法而不是match,即/^find-this-in/.test(str)switch (true)一起使用,它只返回一个布尔值,并且更容易进行大小写不敏感的匹配。
const str = 'haystack';

switch (true) {
  case /^hay.*/i.test(str):
    console.log("Matched a string that starts with 'hay'");
    break;
}

然而,在这种情况下,使用 ifelseelse if 语句也很容易理解。


5
pribilinsiky:你可能需要提到,你的第三个解决方案(使用test())需要在switch(true)中使用。 - traday

35

只需使用location.host属性即可。

switch (location.host) {
    case "xxx.local":
        settings = ...
        break;
    case "xxx.dev.yyy.com":
        settings = ...
        break;
}

2
谢谢,+1,这确实是我应该做的。 - Dr. Frankenstein
你必须注意传递给switch语句的变量类型。它必须是一个字符串。为了确保,你可以这样做:switch ("" + location.host) - ceving

20
另一种选择是使用regexp match resultinput字段:
str = 'XYZ test';
switch (str) {
  case (str.match(/^xyz/) || {}).input:
    console.log("Matched a string that starts with 'xyz'");
    break;
  case (str.match(/test/) || {}).input:
    console.log("Matched the 'test' substring");        
    break;
  default:
    console.log("Didn't match");
    break;
}

不错。在这种情况下,任何数组属性也可以用于测试,例如.length: - Steven Pribilinskiy

7
var token = 'spo';

switch(token){
    case ( (token.match(/spo/) )? token : undefined ) :
       console.log('MATCHED')    
    break;;
    default:
       console.log('NO MATCH')
    break;;
}

如果匹配成功,三元表达式将返回原始令牌。 原始令牌由case语句评估。
如果未匹配成功,三元表达式将返回未定义。 Case语句将令牌与未定义进行比较,希望您的令牌不是未定义。
三元测试可以是任何内容,例如在您的情况下。
( !!~ base_url_string.indexOf('xxx.dev.yyy.com') )? xxx.dev.yyy.com : undefined 

===========================================

(token.match(/spo/) )? token : undefined ) 

这是一个三元表达式

在这种情况下,测试的内容是token.match(/spo/),它声明将token中保存的字符串与正则表达式 /spo/(在此情况下为字面字符串spo)进行匹配。

如果表达式和字符串匹配,则结果为true并返回token(即switch语句正在操作的字符串)。

显然,token === token,因此匹配了switch语句并评估了case。

如果您按层次结构查看它,并理解三元测试是“在”switch语句之前评估的,那么它就更容易理解了,因此switch语句只能看到测试的结果。


你的回答有些令人困惑。你能否重新审查并改进示例和解释? - falsarella
@falsarella,我已经解释了我认为你可能难以理解的部分。我不认为我能够提供更简单的例子了。如果你有更多问题或者能够更具体地描述你的困难,我可以提供更多帮助。 - Tegra Detra
好的,现在我明白了。我之前有些困惑,因为token.match(/spo/)显然是匹配成功的。 - falsarella

3

这可能更容易。试着这样想:

  • 首先捕获两个常规字符之间的字符串
  • 然后找到“case”

:

// 'www.dev.yyy.com'
// 'xxx.foo.pl'

var url = "xxx.foo.pl";

switch (url.match(/\..*.\./)[0]){
   case ".dev.yyy." :
          console.log("xxx.dev.yyy.com");break;

   case ".some.":
          console.log("xxx.foo.pl");break;
} //end switch

已点赞。但请注意:TypeError:url.match(...)为null - 1111161171159459134

1

也许已经晚了,但我喜欢这个任务 :)

function extractParameters(args) {
    function getCase(arg, key) {
        return arg.match(new RegExp(`${key}=(.*)`)) || {};
    }

    args.forEach((arg) => {
        console.log("arg: " + arg);
        let match;
        switch (arg) {
            case (match = getCase(arg, "--user")).input:
            case (match = getCase(arg, "-u")).input:
                userName = match[1];
                break;

            case (match = getCase(arg, "--password")).input:
            case (match = getCase(arg, "-p")).input:
                password = match[1];
                break;

            case (match = getCase(arg, "--branch")).input:
            case (match = getCase(arg, "-b")).input:
                branch = match[1];
                break;
        }
    });
};

你甚至可以更进一步,传递选项列表并使用 | 处理正则表达式。

1
我还会将 || {} 更改为 || [-1] 或类似的内容以实现类型安全。另外,为什么要使用 new RegExp,而不仅仅是斜杠? - Sergey Krasilnikov
我没有真正花时间去完善它...一旦它能工作,我就继续了...现在我感到很羞愧。 - TacB0sS
不要惊慌,那只是我的吹毛求疵;)事实上,我甚至不确定自己是否正确,我试图学习一些新东西。 - Sergey Krasilnikov
不...你说得对...我肯定可以将其泛型化和美化...等我再次处理那段代码时,我会这样做的...希望很快就能到达那个阶段 :) - TacB0sS

0
如果您需要使用正则表达式,可以创建一个带有正则表达式和条件响应的对象来处理switch case
let test = (str) => {
    let obj = {
        'foo':'bar',
        '\/albums?':'photo'
    };
    for(let prop in obj){
        if(new RegExp(prop).test(str))return obj[prop]
    };
};

switch(test(location.href)){
    case 'bar':
        console.log('url has bar')
    break;
}

0

你也可以像这样使用默认情况:

    switch (name) {
        case 't':
            return filter.getType();
        case 'c':
            return (filter.getCategory());
        default:
            if (name.startsWith('f-')) {
                return filter.getFeatures({type: name})
            }
    }

0

自包含版本,提高工作安全性:

switch((s.match(r)||[null])[0])

function identifyCountry(hostname,only_gov=false){
    const exceptionRe = /^(?:uk|ac|eu)$/ ; //https://en.wikipedia.org/wiki/Country_code_top-level_domain#ASCII_ccTLDs_not_in_ISO_3166-1
    const h = hostname.split('.');
    const len = h.length;
    const tld = h[len-1];
    const sld = len >= 2 ? h[len-2] : null;

    if( tld.length == 2 ) {
        if( only_gov && sld != 'gov' ) return null;
        switch(  ( tld.match(exceptionRe) || [null] )[0]  ) {
         case 'uk':
            //Britain owns+uses this one
            return 'gb';
         case 'ac':
            //Ascension Island is part of the British Overseas territory
            //"Saint Helena, Ascension and Tristan da Cunha"
            return 'sh';
         case null:
            //2-letter TLD *not* in the exception list;
            //it's a valid ccTLD corresponding to its country
            return tld;
         default:
            //2-letter TLD *in* the exception list (e.g.: .eu);
            //it's not a valid ccTLD and we don't know the country
            return null;
        }
    } else if( tld == 'gov' ) {
        //AMERICAAA
        return 'us';
    } else {
        return null;
    }
}
<p>Click the following domains:</p>
<ul onclick="console.log(`${identifyCountry(event.target.textContent)} <= ${event.target.textContent}`);">
    <li>example.com</li>
    <li>example.co.uk</li>
    <li>example.eu</li>
    <li>example.ca</li>
    <li>example.ac</li>
    <li>example.gov</li>
</ul>

说实话,你可以像这样做:
function switchableMatch(s,r){
    //returns the FIRST match of r on s; otherwise, null
    const m = s.match(r);
    if(m) return m[0];
    else return null;
}

然后稍后switch(switchableMatch(s,r)){…}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接