看破一道百度面题:正则表达式如何实现JS模板编译🚀

一场风雨,两行泪

面试前,学学 正则表达式如何实现模板编译 🤣

面试官问:下面这段代码怎么实现页面渲染?

yaml 复制代码
let str = '我是{{name}},年龄{{age}},性别{{sex}}'
let person = {
    name: '张三',
    age: 18,
    sex: '男'
}

你:这不简单吗?用 replace 替换,配合正则表达式匹配模板字符串中的占位符就行了。

问: 那你能用函数 compile 来实现一下吗?

正文开始~~~

前言: 模板编译的核心流程(重点!!!)

  • 模板字符串转 AST:将模板字符串解析为抽象语法树(AST),AST 是一个树形结构,包含模板的语法结构(标签、属性、文本节点等)。
  • 优化 AST:通过静态分析,识别静态节点,后续会跳过这些节点的重新渲染。
  • 生成渲染函数:将 AST 转换为渲染函数,渲染函数会生成虚拟 DOM(VNode)。
  • 更新视图:将虚拟 DOM 渲染到页面,实现数据驱动视图更新。

在深入模板编译之前,我们先回顾一下正则表达式的基础知识,因为它是实现模板编译的核心工具之一。

正则表达式是啥 ?

正则表达式(Regular Expressions)是一种强大的文本处理工具,可以用来匹配、查找和替换字符串中的特定模式。下面解释正则主要语法:

1. 匹配特定字符

  • 匹配数字
    • \d:匹配一个数字(0-9)。
    • \d+:匹配一个或多个数字。
    • 示例:/\d+/g 可以匹配字符串中的所有数字。
  • 匹配字母或单词
    • \w:匹配一个字母、数字或下划线([A-Za-z0-9_])。
    • \w+:匹配一个或多个字母、数字或下划线。
    • 示例:/\w+/g 可以匹配字符串中的所有单词。
  • 匹配空白字符
    • \s:匹配一个空白字符(空格、制表符、换行等)。
    • \s+:匹配一个或多个空白字符。
    • 示例:/\s+/g 可以匹配字符串中的所有空白。

2. 匹配特定位置

  • 匹配开头和结尾
    • ^:匹配字符串的开头。
    • $:匹配字符串的结尾。
    • 示例:/^Hello/ 匹配以 Hello 开头的字符串。
  • 匹配单词边界
    • \b:匹配单词的边界(单词的开头或结尾)。
    • 示例:/\bcat\b/ 匹配独立的单词 cat,而不会匹配 category 中的 cat

3. 匹配重复模式

  • 匹配固定次数
    • {n}:匹配前面的字符恰好 n 次。
    • 示例:/\d{3}/ 匹配连续的 3 个数字。
  • 匹配范围次数
    • {n,m}:匹配前面的字符至少 n 次,最多 m 次。
    • 示例:/\d{2,4}/ 匹配 2 到 4 个连续数字。
  • 匹配零次或多次
    • *:匹配前面的字符零次或多次。
    • 示例:/a*/ 可以匹配 ""(空)、"a""aa" 等。
  • 匹配一次或多次
    • +:匹配前面的字符一次或多次。
    • 示例:/a+/ 可以匹配 "a""aa",但不能匹配 ""
  • 匹配零次或一次
    • ?:匹配前面的字符零次或一次。
    • 示例:/a?/ 可以匹配 """a"

4. 匹配字符集合

  • 匹配特定字符
    • [abc]:匹配 abc 中的任意一个字符。
    • 示例:/[aeiou]/g 匹配字符串中的所有元音字母。
  • 匹配字符范围
    • [a-z]:匹配任意小写字母。
    • [A-Z]:匹配任意大写字母。
    • [0-9]:匹配任意数字。
    • 示例:/[a-zA-Z]/g 匹配所有字母(大小写均可)。
  • 排除特定字符
    • [^abc]:匹配除了 abc 之外的任意字符。
    • 示例:/[^0-9]/g 匹配所有非数字字符。

5. 分组和捕获

  • 分组
    • (abc):将 abc 作为一个分组。
    • 示例:/(\d{2})-(\d{2})/ 可以匹配 12-34,并捕获 1234
  • 非捕获分组
    • (?:abc):分组但不捕获。
    • 示例:/(?:\d{2})-(\d{2})/ 匹配 12-34,但只捕获 34

6. 贪婪与非贪婪匹配

  • 贪婪匹配
    • 默认情况下,正则表达式会尽可能多地匹配字符。
    • 示例:/a.*b/ 匹配 "aabab" 中的 "aabab"
  • 非贪婪匹配
    • 在量词后加 ?,表示尽可能少地匹配字符。
    • 示例:/a.*?b/ 匹配 "aabab" 中的 "aab"

7. 常见用途的正则表达式

  • 匹配 URL

    regs 复制代码
    /^(https?://)?([\da-z.-]+).([a-z.]{2,6})([/\w .-]*)*/?$/
  • 匹配手机号(中国大陆)

    regs 复制代码
    /^1[3-9]\d{9}$/
  • 匹配日期(YYYY-MM-DD)

    regs 复制代码
    /^\d{4}-\d{2}-\d{2}$/

从上面可以看出正则用途很多,包括:

  • 验证输入(如邮箱、手机号)。
  • 提取特定格式的文本(如日期、URL)。
  • 替换字符串中的内容(如模板引擎)。

模板编译

在前端开发中,模板编译 允许我们将模板字符串(如 {{name}})转换为实际的 HTML 内容,并根据数据动态更新视图。下面我们以 Vue.js 和 React 为例,简单解释模板编译的实现原理。

1. Vue.js 的模板编译

Vue.js 使用模板引擎来实现数据绑定和视图更新。它的模板语法非常直观,允许开发者在 HTML 中直接嵌入 JavaScript 表达式和指令。例如:

html 复制代码
<div id="app">
  {{ message }}
</div>

在这个例子中,{{ message }} 是一个插值表达式,Vue 的模板编译器会将其转换为渲染函数。渲染函数在运行时根据数据的变化动态更新 DOM。

2. React 的 JSX

React 虽然没有内置的模板引擎,但它使用 JSX(JavaScript XML)来描述 UI。JSX 看起来很像 HTML,但实际上它是 JavaScript 的一种扩展语法。例如:

js 复制代码
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

在这个例子中,<h1>Hello, {props.name}</h1> 是一个 JSX 表达式,它会被 Babel 等工具编译为 React.createElement 函数调用。React 的虚拟 DOM 机制会根据这些函数调用生成实际的 DOM 节点,并在数据变化时高效地更新 DOM。

为什么要使用正则表达式实现模板编译?

它允许我们将模板字符串(如 {{name}})转换为实际的 HTML 内容,并根据数据动态更新视图。使用正则表达式实现模板编译有以下几个优点:

  1. 灵活性:正则表达式可以灵活地匹配各种复杂的模式,适合处理模板字符串中的占位符。
  2. 高效性:正则表达式的匹配和替换操作非常高效,适合处理大量的模板字符串。
  3. 简洁性:使用正则表达式可以大大简化代码,减少重复的逻辑。

使用正则表达式实现模板编译

接下来,我们通过一个简单的例子,使用正则表达式实现模板编译。

1. 基础实现

html 复制代码
<script>
  let str = '我是{{name}},年龄{{age}},性别{{sex}}';
  let person = {
    name: '张三',
    age: 18,
    sex: '男'
  };

  function compile(template, data) {
    let 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(str, person)); // 输出:我是张三,年龄18,性别男
</script>

思路解析

  1. 正则匹配
    • 使用 /{{(\w+)}}/g 匹配模板中的占位符,如 {{name}}
    • (\w+) 捕获 {{}} 中的内容(如 name)。
  2. 提取与替换
    • reg.exec(template) 返回数组,[0] 是完整匹配(如 {{name}}),[1] 是捕获内容(如 name)。
    • 通过 let arr = reg.exec(template)[1]; 提取变量名,从数据中获取对应值。
    • template.replace(reg, value) 替换占位符为实际值。

这时候你为了炫技,又写出其他表达形式

2. 优化实现

js 复制代码
function compile(template,data) {
        let reg = /\{\{([a-z]+)\}\}/g
// 方式一:使用展开运算符
        return template.replace(reg,(...args)=>{
            return data[args[1]]
        })
// 方式二:直接传并且判断path是否还需要替换
        return template.replace(reg,(match,path) => {
            console.log(match,path);
            return path in data ? data[path] : ''
        })
}

这里发一下match,path对应内容,大家应该更好理解:

3. 递归实现

为了确保模板中的所有占位符都被替换,我们可以使用递归:

html 复制代码
<script>
  function compile(template, data) {
    let reg = /{{(\w+)}}/;
    if (reg.test(template)) {
      let key = reg.exec(template)[1];
      let value = data[key] || '';
      template = template.replace(reg, value);
      return compile(template, data); // 递归调用
    } else {
      return template;
    }
  }

  console.log(compile(str, person)); // 输出:我是张三,年龄18,性别男
</script>

我们也看看正则表达式对象执行几次:

性能优化

在实际项目中,模板编译的性能非常重要。给出一些优化建议:

  1. 缓存编译结果:如果模板和数据没有变化,可以缓存编译结果,避免重复编译。
  2. 减少正则匹配次数 :使用全局正则表达式(g 标志)一次性匹配所有占位符。
  3. 使用 AST 优化:对于复杂的模板,可以将其转换为 AST 并进行静态分析,跳过不必要的重新渲染。
相关推荐
大模型铲屎官6 分钟前
大模型(LLM)面试全解:主流架构、训练目标、涌现能力全面解析
人工智能·面试·架构·大模型·llm·nlp·大模型面试
一念永恒@13 分钟前
vue2新增删除
前端·javascript·vue.js
岸边的风30 分钟前
vue中mixin的理解,有那些使用场景?
前端·javascript·vue.js
时间sk1 小时前
CSS——6. 导入样式
前端·css
玩具工匠1 小时前
字玩FontPlayer开发笔记9 Tauri2打包应用
前端·笔记
骆驼Lara1 小时前
Vue3.5 企业级管理系统实战(一):项目初始搭建与配置
前端·vue.js
黑云压城After1 小时前
uniapp web-view调整修改高度设置
前端·javascript·uni-app
问老大1 小时前
uniapp实现在card卡片组件内为图片添加长按保存、识别二维码等功能
前端·javascript·uni-app
她说她一如既往的爱我1 小时前
如何写一个uniapp自定义tarbar导航栏?
前端·vue.js·windows·uni-app
大大艺术家1 小时前
安装vue脚手架出现的一系列问题
前端·javascript·vue.js