广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >JavaScript正则表达式中g标志详解
  • 646
分享到

JavaScript正则表达式中g标志详解

2024-04-02 19:04:59 646人浏览 泡泡鱼
摘要

目录缘起解密过程搜索引擎源码层面结论缘起 有一天在思否社区看到有个问题,大致描述如下 const list = ['a', 'b', '-', 'c', 'd']; const re

缘起

有一天在思否社区看到有个问题,大致描述如下

const list = ['a', 'b', '-', 'c', 'd'];
const reg = /[a-z]/g;
const letters = list.filter(i => reg.test(i));

// letters === ['a', 'c'];
// 如果正则不使用`g`标志可以得到所有的字母
// 为什么加入`g`之后就不可以了

对问题而言,遍历中的i就是一个字符,不需要用到g。

但是就我对正则的理解(过于浅薄)感觉上有没有g(只是全局搜索,不会匹配到就停下来)应该不影响,激发了我的好奇心。

上面题的建议写法如下

const reg = /[a-z]/g;
reg.test('a'); // => true
reg.test('a'); // => false
reg.test('a'); // => true
reg.test('a'); // => false
reg.test('a'); // => true

解密过程

首先可以确定的表现一定是g导致的

搜索引擎

打开 MDN 仔细查看g标志的作用,得到结论和我的理解无二。

我猜想应该就是g可能启用了某种缓存,又因为reg相对过滤器是全局变量,我将代码改为:

const list = ['a', 'b', '-', 'c', 'd'];
const letters = list.filter(i => /[a-z]/g.test(i));

// letters === ['a', 'b', 'c', 'd'];

将正则声明到每一次遍历,得到结论就是正确的,验证了我的猜想。也得到了,缓存就是正则中的某个地方

下面我找到对应的源码来查看问题的原因

源码层面

由于最近在看 Rust,所以使用 Rust 编写的源码查看

打开项目后,点击.进入 vscode 模式,command+p 搜索 regexp 关键词

进入test.rs文件,command+f 搜索/g可以找到在 90 行有个last_index()测试

#[test]
fn last_index() {
    let mut context = Context::default();
    let init = r#"
        var regex = /[0-9]+(\.[0-9]+)?/g;
        "#;
    // forward 的作用:更改 context,并返回结果的字符串。
    eprintln!("{}", forward(&mut context, init));
    assert_eq!(forward(&mut context, "regex.lastIndex"), "0");
    assert_eq!(forward(&mut context, "regex.test('1.0foo')"), "true");
    assert_eq!(forward(&mut context, "regex.lastIndex"), "3");
    assert_eq!(forward(&mut context, "regex.test('1.0foo')"), "false");
    assert_eq!(forward(&mut context, "regex.lastIndex"), "0");
}

看到了有lastIndex关键字,这里再已经大致猜到问题的原因了,g 标志存在匹配后的最后一个下标,导致出现问题。

我们将视线移入到mod.rs文件中,搜索test

在 631 行看到了fn test()方法

pub(crate) fn test(
    this: &jsValue,
    args: &[JsValue],
    context: &mut Context,
) -> JsResult<JsValue> {
    // 1. Let R be the this value.
    // 2. If Type(R) is not Object, throw a TypeError exception.
    let this = this.as_object().ok_or_else(|| {
        context
            .construct_type_error("RegExp.prototype.test method called on incompatible value")
    })?;

    // 3. Let string be ? ToString(S).
    let arg_str = args
        .get(0)
        .cloned()
        .unwrap_or_default()
        .to_string(context)?;

    // 4. Let match be ? RegExpExec(R, string).
    let m = Self::abstract_exec(this, arg_str, context)?;

    // 5. If match is not null, return true; else return false.
    if m.is_some() {
        Ok(JsValue::new(true))
    } else {
        Ok(JsValue::new(false))
    }
}

test()方法中找到了Self::abstract_exec()方法

pub(crate) fn abstract_exec(
    this: &JsObject,
    input: JsString,
    context: &mut Context,
) -> JsResult<Option<JsObject>> {
    // 1. Assert: Type(R) is Object.
    // 2. Assert: Type(S) is String.

    // 3. Let exec be ? Get(R, "exec").
    let exec = this.get("exec", context)?;

    // 4. If IsCallable(exec) is true, then
    if let Some(exec) = exec.as_callable() {
        // a. Let result be ? Call(exec, R, « S »).
        let result = exec.call(&this.clone().into(), &[input.into()], context)?;

        // b. If Type(result) is neither Object nor Null, throw a TypeError exception.
        if !result.is_object() && !result.is_null() {
            return context.throw_type_error("regexp exec returned neither object nor null");
        }

        // c. Return result.
        return Ok(result.as_object().cloned());
    }

    // 5. PerfORM ? RequireInternalSlot(R, [[RegExpMatcher]]).
    if !this.is_regexp() {
        return context.throw_type_error("RegExpExec called with invalid value");
    }

    // 6. Return ? RegExpBuiltinExec(R, S).
    Self::abstract_builtin_exec(this, &input, context)
}

又在Self::abstract_exec()方法中找到了Self::abstract_builtin_exec()方法

pub(crate) fn abstract_builtin_exec(
    this: &JsObject,
    input: &JsString,
    context: &mut Context,
) -> JsResult<Option<JsObject>> {
    // 1. Assert: R is an initialized RegExp instance.
    let rx = {
        let obj = this.borrow();
        if let Some(rx) = obj.as_regexp() {
            rx.clone()
        } else {
            return context.throw_type_error("RegExpBuiltinExec called with invalid value");
        }
    };

    // 2. Assert: Type(S) is String.

    // 3. Let length be the number of code units in S.
    let length = input.encode_utf16().count();

    // 4. Let lastIndex be ℝ(? ToLength(? Get(R, "lastIndex"))).
    let mut last_index = this.get("lastIndex", context)?.to_length(context)?;

    // 5. Let flags be R.[[OriginalFlags]].
    let flags = &rx.original_flags;

    // 6. If flags contains "g", let global be true; else let global be false.
    let global = flags.contains('g');

    // 7. If flags contains "y", let sticky be true; else let sticky be false.
    let sticky = flags.contains('y');

    // 8. If global is false and sticky is false, set lastIndex to 0.
    if !global && !sticky {
        last_index = 0;
    }

    // 9. Let matcher be R.[[RegExpMatcher]].
    let matcher = &rx.matcher;

    // 10. If flags contains "u", let fullUnicode be true; else let fullUnicode be false.
    let unicode = flags.contains('u');

    // 11. Let matchSucceeded be false.
    // 12. Repeat, while matchSucceeded is false,
    let match_value = loop {
        // a. If lastIndex > length, then
        if last_index > length {
            // i. If global is true or sticky is true, then
            if global || sticky {
                // 1. Perform ? Set(R, "lastIndex", +0?, true).
                this.set("lastIndex", 0, true, context)?;
            }

            // ii. Return null.
            return Ok(None);
        }

        // b. Let r be matcher(S, lastIndex).
        // Check if last_index is a valid utf8 index into input.
        let last_byte_index = match String::from_utf16(
            &input.encode_utf16().take(last_index).collect::<Vec<u16>>(),
        ) {
            Ok(s) => s.len(),
            Err(_) => {
                return context
                    .throw_type_error("Failed to get byte index from utf16 encoded string")
            }
        };
        let r = matcher.find_from(input, last_byte_index).next();

        match r {
            // c. If r is failure, then
            None => {
                // i. If sticky is true, then
                if sticky {
                    // 1. Perform ? Set(R, "lastIndex", +0?, true).
                    this.set("lastIndex", 0, true, context)?;

                    // 2. Return null.
                    return Ok(None);
                }

                // ii. Set lastIndex to AdvanceStringIndex(S, lastIndex, fullUnicode).
                last_index = advance_string_index(input, last_index, unicode);
            }

            Some(m) => {
                // c. If r is failure, then
                #[allow(clippy::if_not_else)]
                if m.start() != last_index {
                    // i. If sticky is true, then
                    if sticky {
                        // 1. Perform ? Set(R, "lastIndex", +0?, true).
                        this.set("lastIndex", 0, true, context)?;

                        // 2. Return null.
                        return Ok(None);
                    }

                    // ii. Set lastIndex to AdvanceStringIndex(S, lastIndex, fullUnicode).
                    last_index = advance_string_index(input, last_index, unicode);
                // d. Else,
                } else {
                    //i. Assert: r is a State.
                    //ii. Set matchSucceeded to true.
                    break m;
                }
            }
        }
    };

    // 13. Let e be r's endIndex value.
    let mut e = match_value.end();

    // 14. If fullUnicode is true, then
    if unicode {
        // e is an index into the Input character list, derived from S, matched by matcher.
        // Let eUTF be the smallest index into S that corresponds to the character at element e of Input.
        // If e is greater than or equal to the number of elements in Input, then eUTF is the number of code units in S.
        // b. Set e to eUTF.
        e = input.split_at(e).0.encode_utf16().count();
    }

    // 15. If global is true or sticky is true, then
    if global || sticky {
        // a. Perform ? Set(R, "lastIndex", ?(e), true).
        this.set("lastIndex", e, true, context)?;
    }

    // 16. Let n be the number of elements in r's captures List. (This is the same value as 22.2.2.1's NcapturingParens.)
    let n = match_value.captures.len();
    // 17. Assert: n < 23^2 - 1.
    debug_assert!(n < 23usize.pow(2) - 1);

    // 18. Let A be ! ArrayCreate(n + 1).
    // 19. Assert: The mathematical value of A's "length" property is n + 1.
    let a = Array::array_create(n + 1, None, context)?;

    // 20. Perform ! CreateDataPropertyOrThrow(A, "index", ?(lastIndex)).
    a.create_data_property_or_throw("index", match_value.start(), context)
        .expect("this CreateDataPropertyOrThrow call must not fail");

    // 21. Perform ! CreateDataPropertyOrThrow(A, "input", S).
    a.create_data_property_or_throw("input", input.clone(), context)
        .expect("this CreateDataPropertyOrThrow call must not fail");

    // 22. Let matchedSubstr be the substring of S from lastIndex to e.
    let matched_substr = if let Some(s) = input.get(match_value.range()) {
        s
    } else {
        ""
    };

    // 23. Perform ! CreateDataPropertyOrThrow(A, "0", matchedSubstr).
    a.create_data_property_or_throw(0, matched_substr, context)
        .expect("this CreateDataPropertyOrThrow call must not fail");

    // 24. If R contains any GroupName, then
    // 25. Else,
    let named_groups = match_value.named_groups();
    let groups = if named_groups.clone().count() > 0 {
        // a. Let groups be ! OrdinaryObjectCreate(null).
        let groups = JsValue::from(JsObject::empty());

        // Perform 27.f here
        // f. If the ith capture of R was defined with a GroupName, then
        // i. Let s be the CapturingGroupName of the corresponding RegExpIdentifierName.
        // ii. Perform ! CreateDataPropertyOrThrow(groups, s, capturedValue).
        for (name, range) in named_groups {
            if let Some(range) = range {
                let value = if let Some(s) = input.get(range.clone()) {
                    s
                } else {
                    ""
                };

                groups
                    .to_object(context)?
                    .create_data_property_or_throw(name, value, context)
                    .expect("this CreateDataPropertyOrThrow call must not fail");
            }
        }
        groups
    } else {
        // a. Let groups be undefined.
        JsValue::undefined()
    };

    // 26. Perform ! CreateDataPropertyOrThrow(A, "groups", groups).
    a.create_data_property_or_throw("groups", groups, context)
        .expect("this CreateDataPropertyOrThrow call must not fail");

    // 27. For each integer i such that i ≥ 1 and i ≤ n, in ascending order, do
    for i in 1..=n {
        // a. Let captureI be ith element of r's captures List.
        let capture = match_value.group(i);

        let captured_value = match capture {
            // b. If captureI is undefined, let capturedValue be undefined.
            None => JsValue::undefined(),
            // c. Else if fullUnicode is true, then
            // d. Else,
            Some(range) => {
                if let Some(s) = input.get(range) {
                    s.into()
                } else {
                    "".into()
                }
            }
        };

        // e. Perform ! CreateDataPropertyOrThrow(A, ! ToString(?(i)), capturedValue).
        a.create_data_property_or_throw(i, captured_value, context)
            .expect("this CreateDataPropertyOrThrow call must not fail");
    }

    // 28. Return A.
    Ok(Some(a))
}

Self::abstract_builtin_exec()方法中存在global以及last_index这样看来最终执行的方法就是在这里了,仔细查看该方法中的代码(代码写的很详细而且每一步都有注释)

在第 12 步中:

  • lastIndex 超过文本长度且当 global 存在时将 lastIndex 置为 0
  • 获取匹配到的值(match_value

    如果未匹配到则置为advance_string_index()方法的返回值

    advance_string_index()不在当前问题的考虑范围 https://tc39.es/ecma262/#sec-...

第 13 步获取匹配到的值的 endIndex

第 15 步将 lastIndex 置为 endIndex

至此也就整明白了g标志的含义,在正则的原型链中存在一个lastIndex,如果匹配为真时lastIndex不会重置为 0 ,下一次开始时继承了上次位置,

结论

在问题代码中分析

const reg = /[a-z]/g; // 声明后,lastIndex 为 0
reg.test('a'); // => true;第一次匹配后,lastIndex 为 1
reg.test('a'); // => false;第二次匹配由于 lastIndex 为 1,且字符只有一个,得到 false,将 lastIndex 置为 0
reg.test('a'); // => true;下面依次循环前两次的逻辑
reg.test('a'); // => false;
reg.test('a'); // => true;

到此这篇关于javascript正则表达式中g标志详解的文章就介绍到这了,更多相关js正则中g标志内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: JavaScript正则表达式中g标志详解

本文链接: https://www.lsjlt.com/news/143018.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • JavaScript正则表达式中g标志详解
    目录缘起解密过程搜索引擎源码层面结论缘起 有一天在思否社区看到有个问题,大致描述如下 const list = ['a', 'b', '-', 'c', 'd']; const re...
    99+
    2022-11-13
  • JavaScript正则表达式中g标志实例分析
    本篇内容主要讲解“JavaScript正则表达式中g标志实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JavaScript正则表达式中g标志实例分析”吧!有一天在思否社区看到有个问题,大...
    99+
    2023-06-29
  • JavaScript 正则表达式详解
    目录1. 正则表达式创建2. 使用模式2.1 使用简单模式2.2 使用特殊字符3. 应用3.1 切分字符串3.2 分组3.3 贪婪匹配3.4 正则表达式标志3.5 test() 方法...
    99+
    2022-11-12
  • 详解JavaScript高级正则表达式
    目录JavaScript高级正则表达式1.正则表达式概述1.1什么是正则表达式1.2正则表达式的特点2.正则表达式在js中的使用2.1正则表达式的创建2.2测试正则表达式3.正则表达...
    99+
    2022-11-12
  • Javascript中正则表达式的应用详解
    目录stringsearchreplacematch:RegExp总结正则表达式 在前端中的应用也是比较常见的,我们在有时候也需要 用js 对某些字符串进行查找\捕获 或者 替换. ...
    99+
    2022-11-13
  • Python中正则表达式详解
    基础篇 正则表达式在python中运用的非常多,因为他可以进行任意的匹配,可以匹配我们想要提取的信息。当我们接触正则的时候你就会知道正则的强大。正则有一个库re 在一些工程中我们会经常调用正则的库来做与匹配...
    99+
    2022-06-04
    详解 正则表达式 Python
  • Python 正则表达式详解
    目录1.正则表达式是什么2.1用正则表达式2.2匹配原理2.3常用函数总结1.正则表达式是什么 很简单就是一种字符串匹配,eg: 比如你在注册账户时我们需要对用户的用户名判断是否合法...
    99+
    2022-11-12
  • Ruby正则表达式详解
    目录Ruby 正则表达式语法实例正则表达式修饰符正则表达式模式 正则表达式实例字符 字符类 特殊字符类 重复 非贪婪重复 ...
    99+
    2023-05-15
    Ruby 正则表达式 Ruby 正则表达式详解 Ruby 正则表达式实例
  • Java 正则表达式详解
    正则表达式(Regular Expression),又称为正规表达式、规则表达式、常规表示法等,是一种用来匹配、查找和替换字符串的工...
    99+
    2023-08-16
    Java
  • Python中使用正则表达式及正则表达式匹配规则详解
    目录1 导库2 使用模板3 说明4 示例5 正则表达式匹配规则1 导库 import re 2 使用模板 re_pattern = re.compile(pattern, flags...
    99+
    2023-03-22
    Python正则表达式匹配规则 Python正则表达式
  • JavaScript中正则表达式的实际应用详解
    实际工作中,JavaScript正则表达式还是经常用到的。所以这部分的知识是非常重要的。 一、基础语法: 第一种:字面量语法 var expression=/pattern/f...
    99+
    2022-11-12
  • JavaScript正则表达式exec/g如何实现多次循环
    这篇文章主要介绍了JavaScript正则表达式exec/g如何实现多次循环,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。具体如下:var&...
    99+
    2022-10-19
  • python正则表达式最详解
    目录一、正则表达式–元字符1.数量词2.字符匹配3.边界匹配4.组5.匹配模式参数二、方法re.findallre.matchgroup匹配对象re.searchre.compile...
    99+
    2022-11-12
  • Python3的正则表达式详解
    目录1.简介2.切分字符串3.分组4.贪婪匹配5.编译总结1.简介 # 正则表达式:用来匹配字符串的武器; # 设计思想:用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符...
    99+
    2022-11-13
  • 正则表达式用法详解
    正则表达式之基本概念 在我们写页面时,往往需要对表单的数据比如账号、身份证号等进行验证,而最有效的、用的最多的便是使用正则表达式来验证。那什么是正则表达式呢? 正则表达式(Regul...
    99+
    2022-11-12
  • 怎么理解javascript正则表达式
    本篇内容主要讲解“怎么理解javascript正则表达式”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么理解javascript正则表达式”吧!javascr...
    99+
    2022-10-19
  • 如何理解JavaScript正则表达式
    如何理解JavaScript正则表达式,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。一、正则表达式创建var reg = /test/;var reg = new RegExp...
    99+
    2023-06-02
  • 怎样理解JavaScript 正则表达式
    今天就跟大家聊聊有关怎样理解JavaScript 正则表达式,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。1. 正则表达式创建JavaScript 有两种方式创建正则表达式:第一种:...
    99+
    2023-06-25
  • C#正则表达式与HashTable详解
    目录1、正则表达匹配规则转义字符限定字符分组()2、C#中正则表达式构建与匹配正则表达式的替换正则表达式拆分HashTable概述及元素添加Hashtable遍历Hashtable元...
    99+
    2022-11-13
  • Oracle 正则表达式实例详解
    Oracle 正则表达式实例详解 FORM开发中的按行拆分需求:拆分后的行要有规律,并按前后层次排序   需求分析如下:      现有行: 2 ...
    99+
    2022-10-18
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作