运用正则表达式,实现vue中的模板编译🚀🚀

505 阅读6分钟

疑惑之处:

当你使用vue时,看到这样的代码:

<script setup>
  import { ref } from 'vue'
const name = ref('张三')
const age = ref(18)
const sex = ref('男')
</script>

<template>
   <div>
    '我是{{name}},年龄{{age}},性别{{sex}}'
   </div>
</template>

<style scoped>

</style>

效果: image.png

想要实现这个对于大家来说,那包是简简单单的呀,那有没有想过底层是如何利用js是如何实现的呢?

原题为: 用一个函数实现,对templatename、age、sex的一一替换。你想到用怎么样的方法吗?

let template = '我是{{name}}, 年龄{{age}},性别{{sex}}'
let person = {
  name: '张三',
  age: 18,
  sex: '男'
}

今天我们就来看看,如何用正则表达式,在JS实现上述效果。

正则表达式

今天的重点不在于正则表达式,但是我们可以稍微来了解一下,它的用法:

概念:

正则表达式(Regular Expression,简称regex或regexp)是一种用于匹配字符串中字符模式的工具。它提供了一种灵活且强大的方式来搜索、编辑或操作文本,以及创建输入验证规则等。正则表达式由一系列字符组成,这些字符定义了一个搜索模式。这个模式可以用来查找、编辑或者操作字符串。

案例:

如何利用正则判断一个手机号是否为11位,且开头为1呢?

let reg=/^1[0-9]{10}/
console.log( reg.test('12345678901'))
  • 该正则表述的是一个字符串如果要与这个正则表达式相匹配,它必须以 1 开始,后面紧跟着 10 个数字,总共 11 位。你觉得这样写对吗?
  • / / 指定一个正则表达式字面量的方式。
  • ^1 代表的是一定以一开头
  • [0-9]:这是一个字符集,匹配任何一个从 0 到 9 的数字。
  • {10}:量化符,表示前面的元素(这里是 [0-9] 或者 \d)必须出现恰好 10 次。结合前面的部分,这就意味着接下来的 10 个字符都必须是数字。
  • 当在后面加一个a时,匹配仍然是true,这说明该匹配方法是含有即为true,所以我们需要改进一下 :
  • /^1[0-9]{10}$/ 加个$,确保字符完全符合正则,以11位结束
console.log( reg.test('12345678901a')) // true

如此一来,也算是入门了

实战:

那么对于 template = '我是{{name}}, 年龄{{age}},性别{{sex}}',这段字符串,我们如何写好正则呢? 要知道我们需要获取{{name}}、{{age}}、{{sex}},中的内容,并且替换掉。我们需要了解一些别的用法。

贪婪匹配

默认情况下,+ 是贪婪的(greedy),这意味着它会尽可能多地匹配字符,直到无法再匹配为止。例如,在字符串 "aaab" 中使用模式 /a+/ 来匹配,它将匹配所有的 'a' 字符,即返回 "aaa",而不是只匹配一个或两个 'a'

示例

let str = "aaab";
let regGreedy = /a+/;

console.log(regGreedy.test(str)); // true
console.log(str.match(regGreedy)); // [ 'aaa', index: 0, input: 'aaab', groups: undefined ]
console.log(regGreedy.exec(str));  // [ 'aaa', index: 0, input: 'aaab', groups: undefined ]

String.prototype.match()

  • 全局匹配 (g 标志) :如果正则表达式包含 g(全局)标志,match 返回一个数组,其中包含所有匹配的子字符串。如果不含 g 标志,则只返回第一个匹配项。
  • 捕获组:当使用 match 且没有 g 标志时,它会返回一个数组,其第一个元素是整个匹配结果,后面的元素是捕获组的内容(如果有)。如果指定了 g 标志,则不会返回捕获组的信息。
  • 附加信息:返回的数组还包括三个特殊属性:index(匹配项在原字符串中的索引)、input(原始输入字符串)和 groups(命名捕获组的结果,如果有的话)。

RegExp.prototype.exec()

  • 全局匹配 (g 标志) :无论是否设置了 g 标志,exec 都只会找到下一个匹配项。如果没有更多匹配项,它将返回 null。因此,为了找到所有的匹配项,你需要在一个循环中重复调用 exec
  • 捕获组exec 总是返回一个数组,其中第一个元素是整个匹配结果,后面的元素是捕获组的内容(如果有)。即使有 g 标志,也是一样的。
  • 附加信息:同样地,返回的数组包括 indexinput 和 groups 属性。

匹配template:

let template='我是{{name}},年龄{{age}},性别{{sex}}'
let person={
    name:'张三',
    age:18,
    sex:'男'
}
function compile(template,data){
const reg=/\{\{(\w+)\}\}/
const key =reg.exec(template)[1] // name
reg.exec(template)[0] //{{name}}
let value=data[key]  
console.log(value)  //  输出:张三
}
 compile(template,person)

步骤:

正则的写法

  • 在正则中单单一个{ 代表了其他的含义,想要表示含有{,需要写成\{
  • \w等价于 [a-zA-Z0-9_],即它可以匹配任意的字母、数字或下划线,。
  • () 的目的在于RegExp.prototype.exec()该方法的捕获组识别()内的字母, 如'name'

获取键和对应的值

  • const key = reg.exec(template)[1]:调用 exec 来获取匹配的第一个捕获组,即 name
  • let value = data[key]:从 data 对象中根据 key 获取相应的值,在这里是 '张三'
  • console.log(value):输出这个值,即 '张三'

我们成功的获取了name键值,接下来就是如何替换上去,并且进行多次匹配的问题了。

解题的三种写法:

循环代替:

let template='我是{{name}},年龄{{age}},性别{{sex}}'
let person={
    name:'张三',
    age:18,
    sex:'男'
}

function compile(template,data){

const reg=/\{\{(\w+)\}\}/

while(reg.test(template)){
    let key = reg.exec(template)[1];      // 第二项是分组匹配的内容
    let value = data[key];
    template = template.replace(reg,value);  // 更新
  }
  return template;

}
console.log(compile(template,person)) //我是张三,年龄18,性别男

  • 循环条件为:template 中是否有符合正则的,有就继续循环
  • 找到符合正则的,并进行键值对的替换,然后利用replace的高级用法
  • replace 根据所给的正则,进行相应的替换,这样循环,直到没有{{ }} 可替换的
  • 最后返回template

升级写法:

function compile(template, data) {
    const reg = /{{(\w+)}}/g;

    return template.replace(reg, (match, path) => {
        console.log(match, path); // 用于调试输出
        return path in data ? data[path] : '';
    });
}

1.全局匹配

  • 正则表达式的全局标志 g:通过使用 /g 标志,replace 方法可以一次性找到并替换所有出现的占位符,而不需要显式地循环和管理状态。这使得代码更加简洁且易于理解。

2. 使用回调函数进行替换

  • replace 方法结合回调函数:每次找到一个匹配项时,replace 会调用回调函数,并将匹配结果及其捕获组作为参数传递给该函数。这种做法不仅简化了代码逻辑,还提高了可读性。

    • match 参数:表示与整个正则表达式相匹配的子字符串(例如 {{name}})。
    • path 参数:表示第一个捕获组所匹配的文本(例如 name),即占位符内的键名。

3. 更健壮的数据查找

  • path in data 检查:确保只有当数据对象中存在对应的键时才进行替换。如果不存在,则返回空字符串(或你可以选择保留原始占位符)。这增加了代码的鲁棒性,避免了潜在的错误。

递归:

 if(reg.test(template))
{
     const key =reg.exec(template)[1]
        const value=person[key]
        template=template.replace(reg,value)
     return compile(template,person);
}
       else{
        return template;
       }

    }

与while方法差不多,大家看一看

总结:

今天,学习了一下正则表达式,还实现了vue中{{}}占位符的底层分析。 尚在学习中,如有问题请提出。