前端开发系列054-基础篇之文本插值

本文讨论前端框架\模板中文本插值的实现方案,本文将会主要以[Vue]()框架作为参考讨论文本插值语法的具体实现和推导方案,并补充相关的技术细节。

文本插值

在Vue官网文档的第一部分( 声明式渲染 )我们可以看到下面一段描述。

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统:

<div id="app">
  {{ message }}
</div>

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})
我们已经成功创建了第一个 Vue 应用!
看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。
现在数据和 DOM 已经被建立了关联,所有东西都是响应式的。我们要怎么确认呢?
打开你的浏览器的JavaScript控制台,并修改 app.message 的值,你将看到上例相应地更新。

在Vue官网的另一部分(模板语法-插值)说明了“ Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 HTML 解析器解析 ”。

我们知道,在Vue框架中数据绑定的插值语法使用的是Mustache语法 (双大括号) ,而这篇短小的文章将简单讨论其内部的实现机制。

Class-实例的构建初步

# 标签部分
<div id="app">
  {{ message }}
</div>

# 引入框架文件
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

# 创建Vue实例
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

在Vue框架中我们总是会通过上面的方式来创建并得到一个实例对象,在调用的时候我们传递了一个对象作为构造函数(class)的参数,在该对象中我们设置了挂载的标签(el属性)实例数据(data属性)等信息。

这里,我们先提供一个 构造函数 或者是 Class 来模拟这个整体的结构。

        /* Class的写法 */
        class Manager {
            constructor(o) {
                /*  根据传入的el来获取页面中挂载的标签 */
                this.el = document.querySelector(o.el);

                /* 把对象参数中的data成员(数据)添加到实例对象 */
                /* 在访问的时候可以直接通过(new Manager()).xx访问 */
                for (let key in o.data) {
                    this[key] = o.data[key];
                }
            }
        }
        /* 初始化:传入配置对象创建实例对象 */
        let app = new Manager({
            el: "#app",
            data: {
                message: "Hello 文顶顶!"
            }
        })

在开始的时候,[ 构造函数 \ Class ]的样子可能可能是像上面这样的,先尝试获取参数对象中el的值以获取实例在页面中挂载的标签,然后通过一个循环结构来把data中的数据都直接添加到实例对象,这种处理将允许我们直接以app.message的方式来操作数据。

数据和标签的渲染关系

设计出基本结构后,现在我们可以开始考虑如果需要把data中的数据渲染(绑定)到页面的标签,那该如何实现? 简单思考一秒钟后,我们似乎可以尝试以下的实践策略:

(1) 在初始化的操作中先获取挂载标签的属性节点(这很容易办到,使用innerHTML就可以)。
(2) 在innerHTML中寻找类类似于{{message}}的结构,如果找到那么抠出双括号中的字段-message
(3) 在实例对象中获取-message字段对应的value值,使用该值来替换{{message}}部分。
(!) 因为标签中可能存在多个插值代码,因此可能需要循环处理,在寻找插值代码的时候使用正则匹配或许会比较合适。

下面试着给出用正则来匹配标签内容并进行替换的核心代码,正则表达式的结果可以参考下面的注释,用于匹配 {{ xxx }} 的特定结构,\s*表示可以允许存在空格,\\s表示对\进行转义处理,参数g用以表示应用全局匹配。

let reg = new RegExp(`{{2}\\s*msg\\s*}{2}`, "g"); // /{{2}\s*msg\s*}{2}/g  
this.el.innerHTML = this.el.innerHTML.replace(reg, "文顶顶");

考虑到在参数对象的data中可能会有多个数据(键值对),且执行文本插值的时候某个数据可能会出现在标签的多个位置,因此需要通过循环的方式来检查 innerTTML 字段中每个数据的情况。我们可以通过 Object.keys()方法来获取所有的属性名(key的集合),然后遍历该数组并执行正则替换操作。

<!-- 标签部分 -->
<div id="app">{{ message }}
        <span>{{message}}</span>
        <span>{{msg}}</span>
</div>
<!-- JS代码部分 -->
<script>
        /* Class的写法 */
        class Manager {
            constructor(o) {
                /*  根据传入的el来获取页面中挂载的标签 */
                this.el = document.querySelector(o.el);

                /* 把对象参数中的data成员(数据)添加到实例对象 */
                /* 在访问的时候可以直接通过(new Manager()).xx访问 */
                for (let key in o.data) {
                    this[key] = o.data[key];
                }
                /* 获取data数据中所有的key */
                /* 根据data中的属性集合来遍历渲染页面中指定的内容 */
                Object.keys(o.data).forEach(ele => {
                    let reg = new RegExp(`{{2}\\s*${ele}\\s*}{2}`, "g");
                     /*  /{{2}\s*message\s*}{2}/g */
                     /*  /{{2}\s*msg\s*}{2}/g     */
                    console.log(this.el.innerHTML, reg);
                    this.el.innerHTML = this.el.innerHTML.replace(reg, this[ele]);
                })
            }
        }

        /* 初始化:传入配置对象创建实例对象 */
        let app = new Manager({
            el: "#app",
            data: {
                message: "文顶顶",
                msg: "米桃儿"
            }
        })
</script>

当代码执行的时候,可以看到下面的效果。

至此,便简单了实现了数据-标签渲染的功能。如果数据发生变化后标签中对应的内容也要随之变化,这种数据驱动UI的结构最核心之处在于监听数据的变化并通知给UI视图,具体实现可以参考下一篇文章。

posted on 2022-12-12 10:02  文顶顶  阅读(104)  评论(0编辑  收藏  举报

导航