模板标签翻身记:我用HTML的template加几千行数据,页面却流畅到像假的一样

2026-09-09 0 296

那会儿接到一个需求,用户上传Excel,在后端转成几百条数据,前端要根据这些数据渲染出一个超长的列表。因为后台业务就我一个人维护,自然没打算把Vue或者React塞进去。以前我最爱用字符串拼接,把一条一条商品拼成一个大字符串,最后一插进一个div里。刚开始还行,等数据超过一千条,页面明显感觉卡顿,滚动一下能掉好几帧。而且最难受的是,一不小心数据里有一个`<script>`标签,就直接噩梦。

后来我打算学学别人怎么处理。有人给我推荐了“。他原话是:这玩意就像一个“看不见的土模子”,你需要多少份,就复制多少次,比用字符串快得多。我半信半疑,试完以后发现真的不一样。它不是快那么一点点,而是内存增长都很平滑。

Template到底是什么鬼

HTML里的“标签,你放在页面里,浏览器不会渲染它。不管你写多少内容,用户都看不见,它就像一块不会动的地基。只有通过JavaScript去提取它的内容,然后克隆,在需要的地方插进去才会显示。

有人可能觉得这和`display:none`没啥区别。但有本质不同:display:none里的元素仍然会被浏览器解析,而且它内部的图片、样式都会加载;但`template`里面的东西根本不会被当成页面区域来处理,它更像是一段模板源文本。

举个例子:

<template id="cardTemplate">
    <div class="card">
        <h3 class="title"></h3>
        <p class="desc"></p>
    </div>
</template>

你在页面上看不到那张卡,但模板里的内容已经存在于一个特殊的`DocumentFragment`(文档片段)之中。当我们需要时,把它拿出来克隆一份,填充数据,再放进去,它就和普通DOM一样了。

纯字符串拼接到底慢在哪里

字符串拼接并非慢在连接字符串上,而是最后你用`innerHTML`赋给元素。浏览器要把这一整段HTML源码重新解析一遍,生成对应的节点树,然后再覆盖原来的子节点。数据量一大,字符串解析、节点创建,全部挤在一块,主线程就被卡住了。

而且如果将后端传过来的数据直接拼进HTML,只要有一条里面带了`<img onerror>`,XSS漏洞就开了。你需要转义特殊字符,这又得写函数,又要小心翼翼。我用模板之后呢?只要不去直接给响应式元素塞HTML,我们自然可以避开一堆坑。

动手做一个真正的列表渲染

假设后端返回了一串用户数据:

const remoteData = [
    { name: '张小凡', skill: '烧火棍法', level: 10 },
    { name: '陆雪琪', skill: '神剑御雷真诀', level: 11 },
    // 别嫌少,实际可能有一两千条
];

第一步,定义模板结构。注意模板里没有任何跟某个具体用户相关的文字,只留下等待填充的容器。

<template id="userCardTemplate">
    <div class="user-row">
        <span class="user-name"></span>
        <span class="user-skill"></span>
        <span class="user-level"></span>
    </div>
</template>

第二步,在JS里获取template的content,然后克隆出一个真实节点:

const template = document.getElementById('userCardTemplate');
const newContent = template.content.cloneNode(true);

克隆出来的newContent是一个文档片段,它不是活的元素。你可以对它进行常规DOM操作,比如通过querySelector找到里面的span,并填充文本。这些修改不会触发全局重排,因为片段还没插入文档。

接下来把所有克隆好的片段收集起来,一次性的插入父容器里:

const container = document.getElementById('listContainer');
const fragment = document.createDocumentFragment(); // 使用另一个片段来暂存所有新卡片

remoteData.forEach(user => {
    const card = template.content.cloneNode(true);
    card.querySelector('.user-name').textContent = user.name;
    card.querySelector('.user-skill').textContent = user.skill;
    card.querySelector('.user-level').textContent = 'Lv.' + user.level;
    fragment.appendChild(card);
});

container.appendChild(fragment);

这里的代码跟之前用字符串拼接直观多了:模板里看到的class名,和最后生成的DOM结构完全一致。不用在头晕的双引号地狱里找错误。

性能为什么反而好了

因为模板中的节点早就在解析HTML时被生成过一次,存在template内部。我们需要克隆的是真正的节点树,而不是源码字符串再重新解析。浏览器做克隆是内存级的操作,比解析html源码快得多。同时大量新节点先放在一个独立fragment中,直到最后一次性插入DOM。这个过程中不会触发无数次页面回流。

我拿一千五百条数据试过,首屏渲染在普通笔记本上大约只有几十毫秒到一百毫秒左右,而且是在没有进行虚拟滚动的情况下。如果换成字符串拼接,整个页面可能要白屏好几秒。这差距不止一点点。

顺手还治好了我的XSS焦虑

以前拼接的时候每一条字段都要用安全函数把<和>替换成实体。用了模板就不一样了,咱们用`textContent`赋值,浏览器会直接把它当作纯文本处理。哪怕用户提交了一条恶意文本<img src=x onerror=alert(1)>,它也会原样显示成文字,绝不会被解析成HTML。就这一条,我晚上睡觉都踏实了不少。

如果你偏要在模板里通过`innerHTML`填数据,那还是有风险,但那是自找的。好的习惯是:像标题、内容这种来自外部数据,一律用textContent。

模板不止用于列表

后来我还用它来做弹窗里的表单片段、右键菜单、甚至复杂表格的表头。因为模板可以定义任意的结构,你可以把它看成一段可以无限复制的“组件骨架”。如果你需要根据后台数据动态生成几个配置项,那么模板的循环、赋值会变得很清晰。

神奇的是,你可以把模板放在页面任何位置,不一定放body的最后。但按惯例还是把它写在需要的容器旁边,用id标记。这样团队的其他人一看就知道哪个模板对应哪个模块。

进一步改进:利用模板配合事件委托

列表渲染出来后,可能线上每个卡片上都有一个“点击删除”按钮。你不需要给每个按钮单独绑定事件,只要在父容器上监听一次click。在事件回调里,通过event.target判断是否点击到指定class,再把当前行的数据取出来。

container.addEventListener('click', function(event) {
    if (event.target.classList.contains('delete-btn')) {
        const row = event.target.closest('.user-row');
        const userName = row.querySelector('.user-name').textContent;
        // 执行删除
        row.remove();
    }
});

这不算什么新知识,但和模板配合起来,整个代码逻辑很顺:模板只管长得像什么样,事件管理独立于模板在父层做委托。不需要像某些框架那样搞出一套虚拟DOM再diff,一样能达到期望的交互效果。

一个容易被忽略的坑

需要特别提醒:很多人喜欢把模板的内容写在“只后,然后想在JS里用`innerHTML`来更新模板内部,他们说模板里的元素在DOM中不存在,导致访问不到。其实不是不存在,而是你需要先把它克隆出来。注意是`cloneNode(true)`,如果参数不是true,只会复制模板本身,里面的内容不会复制。

还有一个点:如果你在一个动态表格中要给每个卡片添加不同的id,或者设置不同dataset属性。要在填充文本时一起设置:

card.dataset.userId = item.id;

不要想着等插入后再逐个去更新,那时会导致额外重排,但也没多大问题。

还有没有更强的用法

你知道吗,这种模式被现代框架变成了一种“内部魔法”。Vue的模板编译后其实会生成类似的render函数去创建vnode,但最终操作DOM时不还是用原生方法?我们没有框架,自己用“来处理纯前端渲染,其实就是自己实现了轻量级的“组件片段管理”。

如果再用上Shadow DOM,每个模板克隆出来的部分还能附带独立的样式作用域,但那就扯得更远了。单单是配合“,就已经能为老项目减少80%的DOM操作代码。

直接手写一个完整demo

最后展示一个可直接运行的代码片段,一个简单的表格渲染。这个demo只用了HTML和极简的JavaScript,没有任何库。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>template基础demo</title>
</head>
<body>

    <template id="listItemTpl">
        <tr>
            <td class="item-name"></td>
            <td class="item-price"></td>
            <td><button class="deleteBtn">删除</button></td>
        </tr>
    </template>

    <table>
        <thead>
            <tr>
                <th>名称</th>
                <th>价格</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody id="tableBody"></tbody>
    </table>

    <script>
        const products = [
            { name: '键盘', price: '¥199' },
            { name: '鼠标', price: '¥99' },
            { name: '显示器', price: '¥1299' },
        ];

        const tbody = document.getElementById('tableBody');
        const tpl = document.getElementById('listItemTpl');

        const fragment = document.createDocumentFragment();

        products.forEach((product, index) => {
            const row = tpl.content.cloneNode(true);
            row.querySelector('.item-name').textContent = product.name;
            row.querySelector('.item-price').textContent = product.price;

            const deleteBtn = row.querySelector('.deleteBtn');
            deleteBtn.addEventListener('click', function handler() {
                tbody.removeChild(row);
            });

            fragment.appendChild(row);
        });

        tbody.appendChild(fragment);
    </script>

</body>
</html>

实际运行你会发现和普通构造的DOM没区别,但它的语义很清晰——模板负责布局,JS只负责填数据和处理事件。

最后

HTML里一直有一堆我们没好好珍惜的标签,`template`就是其中一个。它可能没有popover那么扎眼,也没有dialog那么功能明确,但它在构建大量重复DOM时确实是一把好手。如果你也想在不用框架的情况下解决复杂列表渲染,我建议你试试它。

我现在已经彻底放弃字符串拼接做界面了。只要遇到重复的视图单元,第一反应就是写个template。除了干净安全,更重要的是省下来的时间足以让我多刷几个论坛。

模板标签翻身记:我用HTML的template加几千行数据,页面却流畅到像假的一样
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请发送邮件1506151422@qq.com联系,将会及时下架删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 html 模板标签翻身记:我用HTML的template加几千行数据,页面却流畅到像假的一样 https://www.taomawang.com/web/html/2735.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务