ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

原生JS与jQuery append添加div的写法、性能对比与实战踩坑指南

原生JS与jQuery append添加div的写法、性能对比与实战踩坑指南 只要写过几年前端肯定绕不开往页面上动态添加元素这回事。用append往容器里塞div看起来就是一行代码的事但真到项目里字符串拼接、DOM对象、批量循环、事件绑定、样式兼容每个环节都能踩出坑来。这篇文章我会从原生JS写到jQuery把append添加div的常见写法、性能对比、以及那些“文档里查不到”的实战细节一次讲透。先说清楚这篇文章适合谁看刚入门的前端新手被老师的$(div).append(...)教过、但不知道底层在干嘛写了两三年业务、想系统查漏补缺的进阶开发者还有那些在维护老项目、必须跟jQuery打交道的同学。文章不会有太高深的理论全部围绕“用append添加div”这一个点把原生和jQuery对照着讲再补上实际项目中一定会碰到的性能、事件、选择器这些衍生问题。1. 先分清两个“append”原生JS和jQuery的本质差异1.1 原生DOM里到底有没有append方法很多人以为原生JS只有appendChild其实浏览器早就提供了append方法。Element.append()是DOM规范里较新的接口它和appendChild最直观的区别在于两点append可以同时添加多个节点和字符串appendChild只能接收一个节点对象append没有返回值appendChild会返回被追加的节点。看一段最直接的对比// 原生 appendChild const box document.getElementById(box); const div document.createElement(div); div.textContent 我是子元素; box.appendChild(div); // 原生 append const box2 document.getElementById(box); box2.append(可以直接塞字符串, document.createElement(span)); // appendChild 如果传字符串会报错 // box.appendChild(字符串) // TypeError这里有个实际场景很常用你想在容器最后追加一段HTML文本用append可以直接写box.append(div classitem动态添加/div);结果会以Text节点形式插入所以页面上能看到字符串内容但它不是真正的div元素。如果你要的是一个真实可操作的div节点就得用insertAdjacentHTML或者先createElement再append。1.2 jQuery的append是一个家族方法jQuery里的append要复杂得多它背后是一整套操作集合。很多人只记住了$(#box).append(content)这种用法其实jQuery围绕“往容器末尾加东西”设计了好几个方法具体区别我来拆一下方法作用位置语义.append(content)在容器内部末尾追加把内容放到选中元素里面、最后面.prepend(content)在容器内部开头追加把内容放到选中元素里面、最前面.appendTo(target)把选中元素追加到目标容器末尾和append方向相反是“我把自己塞进你里面”.prependTo(target)把选中元素追加到目标容器开头同样是反向操作.after(content)在选中元素后面插入追加的是兄弟节点不是子节点.before(content)在选中元素前面插入也是兄弟节点看代码就清楚了// append把div加到box里 $(#box).append(div classitem新来的/div); // appendTo把新建的div加到box里 $(div classitem新来的/div).appendTo(#box); // after加在box外面作为哥哥后面的兄弟 $(#box).after(div classitem我是box后面/div);这两个方向一定要分清。append是“以容器为主体往里塞东西”appendTo是“以内容为主体把自己塞进容器”。老项目里经常能看到$(div/).appendTo(body)这种写法就是典型的“创建完直接插入”风格。1.3 为什么append可以传HTML字符串jQuery的append能直接识别div classitem内容/div这种字符串并创建真实DOM本质是jQuery内部使用了innerHTML机制。这里有一个很多人没注意到的关键点当append的字符串比较复杂时jQuery会尝试提取核心HTML但如果你的字符串是一个单纯的文本节点jQuery会走文本创建逻辑。我举个日常例子// 这种会正常解析成div $(#box).append(div classitem内容/div); // 这种会是纯文本不会变成div $(#box).append(hello world);如果你把变量拼进去一定要保证变量里没有意外的HTML标签。否则要么XSS风险要么页面结构被莫名其妙地破坏。比如let userInput img srcx onerroralert(1); $(#box).append(div userInput /div);这种写法会把用户输入当成HTML解析轻则样式错乱重则脚本注入。基于安全考虑只要内容里带用户数据我建议要么用text()方法写入、要么先createElement再textContent赋值尽量不要直接拼在HTML字符串里。2. 六种最常见的添加div写法与选择思路2.1 字符串拼接法最快但最脆弱最简单的写法就是把HTML片段拼成字符串直接塞进去原生和jQuery都能做// 原生 document.getElementById(box).innerHTML div classitem新div/div; // 原生insertAdjacentHTML document.getElementById(box).insertAdjacentHTML(beforeend, div classitem新div/div); // jQuery $(#box).append(div classitem新div/div);innerHTML 这个写法我一般不推荐因为每次都会把整个容器的HTML序列化再重写哪怕只加一个子元素也会导致所有子元素被销毁重建。后果很直接子元素上绑定的JS事件全部丢失、表单状态重置、图片重新加载。多个元素循环添加时性能惨不忍睹。相比之下insertAdjacentHTML和jQuery的append会好很多前者是直接解析并插入新片段不触碰原有DOM结构。2.2 createElement方案可操作但代码量大用document.createElement创建div再用appendChild或append插入是原生JS的标准做法const div document.createElement(div); div.className item; div.id item- Date.now(); div.textContent 我是动态创建的div; div.dataset.index 1; document.getElementById(box).appendChild(div);这种写法最大的好处是完全可控。字段一个个设置事件直接在上面绑定拿到的是真实DOM对象后续修改也方便。缺点是代码量大创建10个div就要写10遍。实际项目里一般配合map、forEach来做批量生成const data [苹果, 香蕉, 橘子]; const box document.getElementById(box); data.forEach((name, index) { const div document.createElement(div); div.className fruit-item; div.textContent name; div.dataset.index index; box.appendChild(div); });2.3 insertAdjacentHTML原生里的“小jQuery”insertAdjacentHTML可能是原生API里最被低估的一个它的用法非常接近jQuery的appendconst box document.getElementById(box); box.insertAdjacentHTML(beforeend, div classitem尾部插入div/div); box.insertAdjacentHTML(afterbegin, div classitem头部插入div/div);四个位置参数参数插入位置beforebegin元素自身之前兄弟位置afterbegin元素内部、第一个子节点之前beforeend元素内部、最后一个子节点之后afterend元素自身之后兄弟位置这个API的好处就是不需要额外创建节点直接把HTML字符串解析成DOM并插入效率比innerHTML 高得多而且不破坏已有子元素。我封装动态列表的时候经常用它代码简洁程度不输jQuery。2.4 实际项目里怎么选给个选择优先级供参考内容完全可信、且只是一次性插入insertAdjacentHTML或jQuery的append需要绑定事件、绑定数据、后续频繁改样式createElement方案循环插入大量元素DocumentFragment批量处理第三部分细讲维护老jQuery项目优先用.append()但要注意和原生DOM交错使用时的坑。3. 性能与踩坑循环添加div时必须知道的事3.1 循环里反复append为什么慢这是个老生常谈但又值得反复强调的问题。每append一次浏览器就要做一次布局计算和渲染更新。如果循环100次操作DOM浏览器就要可能触发100次重排。体感是什么页面卡顿、滚动掉帧、CPU飙升。举个例子感受一下// 性能差循环里反复append const box document.getElementById(box); for (let i 0; i 500; i) { const div document.createElement(div); div.textContent 第 i 个; box.appendChild(div); }这种写法500个元素可能还好一旦上到5000甚至几万个页面会明显卡顿。以前我刚入行时不知道这个坑循环几千个节点直接在页面上崩了一次后来才知道要用DocumentFragment。3.2 DocumentFragment批量插入不卡顿DocumentFragment可以理解为一个“虚拟容器”它存在于内存中不属于页面DOM树。你先往里面塞N个节点最后只做一次真实DOM操作把整个fragment插进容器。这样布局计算只触发一次性能天差地别const box document.getElementById(box); const fragment document.createDocumentFragment(); for (let i 0; i 1000; i) { const div document.createElement(div); div.className item; div.textContent 第 i 个div; fragment.appendChild(div); } box.appendChild(fragment);重点在于fragment插入后自身会清空页面里得到的是它包含的那些div。这个特性能帮你省掉很多麻烦比如不需要在插入后清空临时容器。jQuery时代也有类似的人为约定用一个临时div当容器全部append进去最后一次性$(#box).append(tempDiv.childNodes)。3.3 循环出来的div一行两个如何选最后两个元素这个需求听起来很奇怪但实际项目中真的会遇到。比如商品列表每行展示两个div最后一行如果不满两个UI要求最后两个不加某种样式。热搜词里也出现了“循环出来的div 一行两个如何算最后两个元素”和“最后两个元素不加伪类”说明不少人卡在CSS和JS双重限制上。先明确一个概念CSS伪类last-child、nth-last-child可以选最后两个元素但当元素不是最后一个时伪类的判断会错位。比如一行两个div的列表你想让倒数第一个和倒数第二个不加边框写成.item:last-child, .item:nth-last-child(2) { border: none; }这个写法在纯静态场景下是正确的但如果最后一行只有两个div而倒数第二个和倒数第三个是同一行的那可就不一定是UI想要的效果了。真实的做法通常要结合请求回来的数据长度和当前行数来判断// 假设每行2个 data.forEach((item, index) { const div document.createElement(div); div.className item; div.textContent item.name; // 最后一行 const totalRows Math.ceil(data.length / 2); const currentRow Math.floor(index / 2) 1; const isLastRow currentRow totalRows; // 最后一行第一个元素也不需要右边框可以动态加类 if (isLastRow) { div.classList.add(last-row-item); } list.appendChild(div); });这个方法虽然代码多但判断逻辑完全可控不会被CSS伪类的“最后一个”语义限制住。如果你想看纯CSS能不能解决在结构完全规律的情况下可以给容器设置每行宽度然后直接用:nth-last-child(-n2)选中倒数两个配合:not(:nth-last-child(-n2))处理前面的边框也能应付但前提是每个item宽度固定且确实是最后一行。我个人的经验是涉及动态数据列表能不用CSS伪类就别用JS算好再打类名最稳。4. 动态添加div后的三大“灵魂拷问”4.1 事件绑定为什么失效这是日常踩坑排行榜第一名。现象是页面加载后用append添加的div点击没反应。原因很简单直接绑定在已有元素上的事件不会自动作用于未来创建的元素。// 错误示范动态div没有点击事件 $(.item).on(click, function() { console.log(点击了); }); $(#box).append(div classitem动态div/div);上面代码里$(.item)在事件绑定时页面里还没有任何.item元素或者只绑定了当时存在的元素后面append进来的div当然没有事件。解决办法就是事件委托// 正确做法把监听挂到容器上事件通过冒泡触发 $(#box).on(click, .item, function() { console.log(点击了, this); });原生JS同样可以委托document.getElementById(box).addEventListener(click, function(e) { const target e.target.closest(.item); if (target) { console.log(点击了, target); } });事件委托的原理是子元素的事件会冒泡到父容器父容器统一监听再通过e.target判断点击的是谁。这样一来不管以后往容器里加多少个div事件都能正常工作因为它监听的是容器本身而不是某个特定子元素。4.2 动态添加的div为什么没有样式这个问题通常是两种原因。一种是因为样式写在了某个局部作用域里动态加载的DOM不在这个范围内比如Vue的scoped样式只对当前组件模板中的静态部分生效另一种是你加了class但CSS规则优先级不够。纯jQuery或者原生场景最常见的是拼接字符串时把class拼错了div classitem内容/div这种引号错位会导致整个class解析失败。排查办法很简单打开控制台用$0选中刚插入的元素看一下它的className值再检查CSS选择器写的是不是暗合。如果是项目用了CSS Modules或者BEM动态插入的div里写的类名和生成后的哈希类名对不上样式自然就丢了。可以先把元素的outerHTML复制出来检查问题多半一眼就能看出来。4.3 动态添加后怎么获取元素添加完div后很多人下意识去getElementById结果拿到的是null。原因往往是插入前后DOM还没更新到渲染层很少见但某些老浏览器里有元素的id里带了引号没转义拼字符串时id写成了动态变量但取值不对。获取动态元素最稳的方法插入后立刻用容器查询或者干脆在创建时就把变量存起来引用。// 推荐方式直接保存引用 const div document.createElement(div); div.id item- index; box.appendChild(div); // 之后直接用div变量就行不用再查找 // 如果必须要查询 const findDiv document.getElementById(item- index); // 慎用id里有特殊字符时可能失效推荐用querySelector搭配属性选择器const target box.querySelector([data-id index ]);把>const box document.getElementById(box); // 渲染列表 function renderList(data) { let html ; data.forEach(item { html div classitem>function escapeHtml(str) { return String(str) .replace(//g, amp;) .replace(//g, lt;) .replace(//g, gt;) .replace(//g, quot;) .replace(//g, #39;); }这个函数虽然只覆盖了最基础的字符但已经能挡住大部分意外的HTML注入问题。放在工具函数里拼接时给变量包一层box.insertAdjacentHTML(beforeend, div${escapeHtml(userInput)}/div);有人会觉得麻烦但踩过一次XSS或者页面被改乱的坑就不嫌麻烦了。写到最后我想说append这个操作本身只是一个入口真正决定代码质量的往往是你围绕这个入口做的选择用什么方式创建节点、怎么批量插入、事件怎么绑定、内容怎么处理。这些细节组合起来才是日常业务代码和高品质代码之间真正的差距。把自己踩过的坑、养成的好习惯沉淀下来比记多少API都管用。
返回列表