活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

深入理解HTML DOM节点创建与插入技术 前端开发必备技能 从基础到实战全面解析动态页面构建方法

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-12 16:10:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
简介

在现代Web开发中,动态页面构建已成为前端开发的核心技能之一。DOM(文档对象模型)作为HTML和XML文档的编程接口,允许开发者通过JavaScript动态地访问和更新文档的内容、结构和样式。掌握DOM节点的创建与插入技术,不仅能够提升用户体验,还能实现复杂的交互效果和单页应用(SPA)的构建。本文将深入探讨DOM节点创建与插入的各种方法,从基础概念到实战应用,帮助前端开发者全面理解动态页面构建的技术细节。

DOM基础概念

什么是DOM

DOM(Document Object Model)是HTML和XML文档的编程接口,它将文档表示为一个节点树,其中每个节点代表文档的一部分(如元素、属性、文本等)。通过DOM,开发者可以使用JavaScript等脚本语言动态地访问和修改文档的内容、结构和样式。
  1. // 整个文档是一个文档节点
  2. document.documentElement; // 获取<html>元素
  3. // 所有HTML元素都是元素节点
  4. document.getElementById('header'); // 获取id为header的元素
  5. // 元素内的文本是文本节点
  6. const textNode = document.createTextNode('这是一段文本');
复制代码

DOM树结构

DOM将HTML文档表示为树形结构,其中每个节点都有父节点、子节点和兄弟节点的关系。这种树形结构使得开发者可以轻松地遍历和操作文档的各个部分。
  1. <!DOCTYPE html>
  2. <html>
  3.   <head>
  4.     <title>示例页面</title>
  5.   </head>
  6.   <body>
  7.     <h1>标题</h1>
  8.     <p>段落文本</p>
  9.   </body>
  10. </html>
复制代码

上述HTML对应的DOM树结构如下:
  1. Document
  2. └── html
  3.      ├── head
  4.      │   └── title
  5.      │       └── "示例页面"
  6.      └── body
  7.          ├── h1
  8.          │   └── "标题"
  9.          └── p
  10.              └── "段落文本"
复制代码

节点类型

DOM定义了多种节点类型,每种类型都有其特定的属性和方法。常见的节点类型包括:

1. 元素节点(Element Node):表示HTML元素,如<div>、<p>等。
2. 文本节点(Text Node):表示元素中的文本内容。
3. 属性节点(Attribute Node):表示元素的属性。
4. 文档节点(Document Node):表示整个文档。
5. 文档片段节点(DocumentFragment Node):表示文档的轻量级”部分”。
  1. // 检查节点类型
  2. const element = document.createElement('div');
  3. console.log(element.nodeType); // 1 (元素节点)
  4. const text = document.createTextNode('Hello');
  5. console.log(text.nodeType); // 3 (文本节点)
  6. const attr = document.createAttribute('class');
  7. console.log(attr.nodeType); // 2 (属性节点)
  8. console.log(document.nodeType); // 9 (文档节点)
  9. const fragment = document.createDocumentFragment();
  10. console.log(fragment.nodeType); // 11 (文档片段节点)
复制代码

DOM节点创建方法

createElement方法

createElement是创建新元素节点最常用的方法。它接受一个参数,即要创建的元素的标签名,并返回一个新的元素节点。
  1. // 创建一个div元素
  2. const newDiv = document.createElement('div');
  3. // 设置元素属性
  4. newDiv.id = 'myDiv';
  5. newDiv.className = 'container';
  6. newDiv.setAttribute('data-role', 'main');
  7. // 设置样式
  8. newDiv.style.color = 'blue';
  9. newDiv.style.padding = '10px';
  10. // 此时元素已创建但尚未添加到DOM中
  11. console.log(newDiv); // <div id="myDiv" class="container" data-role="main" style="color: blue; padding: 10px;"></div>
复制代码

createTextNode方法

createTextNode方法用于创建新的文本节点。它接受一个参数,即文本内容,并返回一个新的文本节点。
  1. // 创建文本节点
  2. const textContent = document.createTextNode('这是一段文本');
  3. // 创建元素并添加文本节点
  4. const paragraph = document.createElement('p');
  5. paragraph.appendChild(textContent);
  6. // 添加到DOM
  7. document.body.appendChild(paragraph);
  8. // 也可以使用textContent或innerText属性直接设置文本
  9. const anotherParagraph = document.createElement('p');
  10. anotherParagraph.textContent = '这是另一段文本';
  11. document.body.appendChild(anotherParagraph);
复制代码

createDocumentFragment方法

createDocumentFragment方法创建一个文档片段节点,这是一种轻量级的DOM节点,可以包含其他节点,但不会成为实际DOM树的一部分。当需要添加多个节点到DOM时,使用文档片段可以提高性能,因为它只会触发一次重排。
  1. // 创建文档片段
  2. const fragment = document.createDocumentFragment();
  3. // 创建多个列表项
  4. const items = ['苹果', '香蕉', '橙子', '葡萄'];
  5. items.forEach(itemText => {
  6.   const li = document.createElement('li');
  7.   li.textContent = itemText;
  8.   fragment.appendChild(li);
  9. });
  10. // 一次性将所有列表项添加到DOM
  11. const ul = document.createElement('ul');
  12. ul.appendChild(fragment);
  13. document.body.appendChild(ul);
复制代码

cloneNode方法

cloneNode方法用于复制节点,它接受一个布尔参数,表示是否进行深度复制(即复制节点的所有后代节点)。
  1. // 创建原始元素
  2. const originalDiv = document.createElement('div');
  3. originalDiv.className = 'box';
  4. originalDiv.innerHTML = '<p>原始内容</p>';
  5. // 浅拷贝(只复制节点本身,不复制子节点)
  6. const shallowClone = originalDiv.cloneNode(false);
  7. console.log(shallowClone.innerHTML); // "" (空)
  8. // 深拷贝(复制节点及其所有子节点)
  9. const deepClone = originalDiv.cloneNode(true);
  10. console.log(deepClone.innerHTML); // "<p>原始内容</p>"
  11. // 添加到DOM
  12. document.body.appendChild(originalDiv);
  13. document.body.appendChild(deepClone);
复制代码

使用innerHTML和outerHTML

虽然不是直接的节点创建方法,但innerHTML和outerHTML属性可以快速创建和插入HTML结构。
  1. // 创建一个容器
  2. const container = document.createElement('div');
  3. // 使用innerHTML设置内容
  4. container.innerHTML = `
  5.   <h2>标题</h2>
  6.   <p>这是一个段落</p>
  7.   <ul>
  8.     <li>项目1</li>
  9.     <li>项目2</li>
  10.     <li>项目3</li>
  11.   </ul>
  12. `;
  13. // 添加到DOM
  14. document.body.appendChild(container);
  15. // 使用outerHTML替换整个元素
  16. const oldElement = document.getElementById('oldElement');
  17. if (oldElement) {
  18.   oldElement.outerHTML = '<div id="newElement">新元素</div>';
  19. }
复制代码

注意:使用innerHTML和outerHTML时要注意XSS(跨站脚本攻击)风险,特别是当内容来自用户输入时。

DOM节点插入方法

appendChild和insertBefore

appendChild方法将一个节点添加到指定父节点的子节点列表的末尾,而insertBefore方法将一个节点插入到指定子节点之前。
  1. // 创建父元素
  2. const parent = document.createElement('div');
  3. parent.id = 'parent';
  4. // 创建子元素
  5. const firstChild = document.createElement('p');
  6. firstChild.textContent = '第一个子元素';
  7. firstChild.id = 'first';
  8. const secondChild = document.createElement('p');
  9. secondChild.textContent = '第二个子元素';
  10. secondChild.id = 'second';
  11. const thirdChild = document.createElement('p');
  12. thirdChild.textContent = '第三个子元素';
  13. thirdChild.id = 'third';
  14. // 使用appendChild添加子元素
  15. parent.appendChild(firstChild);
  16. parent.appendChild(thirdChild);
  17. // 使用insertBefore在指定节点前插入子元素
  18. parent.insertBefore(secondChild, thirdChild);
  19. // 添加到DOM
  20. document.body.appendChild(parent);
  21. // 结果结构:
  22. // <div id="parent">
  23. //   <p id="first">第一个子元素</p>
  24. //   <p id="second">第二个子元素</p>
  25. //   <p id="third">第三个子元素</p>
  26. // </div>
复制代码

insertAdjacentHTML和insertAdjacentElement

insertAdjacentHTML方法将HTML字符串解析为元素或节点,并将结果插入到DOM中的指定位置。insertAdjacentElement方法则将一个元素节点插入到指定位置。

这两个方法都接受两个参数:位置(字符串)和要插入的内容(HTML字符串或元素节点)。位置可以是以下四个值之一:

• 'beforebegin':在元素之前插入
• 'afterbegin':在元素内部的开始处插入
• 'beforeend':在元素内部的结束处插入
• 'afterend':在元素之后插入
  1. // 创建目标元素
  2. const target = document.createElement('div');
  3. target.id = 'target';
  4. target.innerHTML = '目标元素';
  5. document.body.appendChild(target);
  6. // 使用insertAdjacentHTML插入HTML字符串
  7. target.insertAdjacentHTML('beforebegin', '<p>在目标元素之前</p>');
  8. target.insertAdjacentHTML('afterbegin', '<p>在目标元素内部的开始处</p>');
  9. target.insertAdjacentHTML('beforeend', '<p>在目标元素内部的结束处</p>');
  10. target.insertAdjacentHTML('afterend', '<p>在目标元素之后</p>');
  11. // 使用insertAdjacentElement插入元素节点
  12. const newElement = document.createElement('div');
  13. newElement.textContent = '新插入的元素';
  14. target.insertAdjacentElement('afterend', newElement);
复制代码

append和prepend方法

append和prepend是较新的DOM方法,分别用于在父元素的子节点列表的末尾和开始处添加节点或字符串。与appendChild不同,这些方法可以同时接受多个参数,并且可以添加字符串作为文本节点。
  1. // 创建父元素
  2. const container = document.createElement('div');
  3. container.id = 'container';
  4. // 创建子元素
  5. const header = document.createElement('h1');
  6. header.textContent = '标题';
  7. const footer = document.createElement('p');
  8. footer.textContent = '页脚';
  9. // 使用prepend在开始处添加子元素
  10. container.prepend(header);
  11. // 使用append在结束处添加子元素
  12. container.append(footer);
  13. // 同时添加多个元素和文本
  14. container.append('一些文本', document.createElement('br'), '更多文本');
  15. // 添加到DOM
  16. document.body.appendChild(container);
  17. // 结果结构:
  18. // <div id="container">
  19. //   <h1>标题</h1>
  20. //   一些文本<br>更多文本
  21. //   <p>页脚</p>
  22. // </div>
复制代码

replaceChild方法

replaceChild方法用于替换父节点中的子节点。它接受两个参数:新节点和要被替换的旧节点。
  1. // 创建父元素和初始子元素
  2. const parent = document.createElement('div');
  3. parent.id = 'parent';
  4. const oldChild = document.createElement('p');
  5. oldChild.textContent = '旧的子元素';
  6. oldChild.id = 'old';
  7. parent.appendChild(oldChild);
  8. document.body.appendChild(parent);
  9. // 创建新元素
  10. const newChild = document.createElement('div');
  11. newChild.textContent = '新的子元素';
  12. newChild.id = 'new';
  13. // 使用replaceChild替换子元素
  14. parent.replaceChild(newChild, oldChild);
  15. // 结果:旧的p元素被新的div元素替换
复制代码

实战案例

动态列表创建

在实际开发中,经常需要根据数据动态创建列表。以下是一个基于数组数据创建有序列表的完整示例。
  1. // 数据源
  2. const products = [
  3.   { id: 1, name: '笔记本电脑', price: 5999 },
  4.   { id: 2, name: '智能手机', price: 3999 },
  5.   { id: 3, name: '平板电脑', price: 2999 },
  6.   { id: 4, name: '智能手表', price: 1299 }
  7. ];
  8. // 创建列表容器
  9. const productList = document.createElement('div');
  10. productList.className = 'product-list';
  11. // 创建标题
  12. const title = document.createElement('h2');
  13. title.textContent = '产品列表';
  14. productList.appendChild(title);
  15. // 创建列表元素
  16. const list = document.createElement('ul');
  17. list.className = 'products';
  18. // 使用文档片段优化性能
  19. const fragment = document.createDocumentFragment();
  20. // 遍历数据创建列表项
  21. products.forEach(product => {
  22.   // 创建列表项
  23.   const listItem = document.createElement('li');
  24.   listItem.className = 'product-item';
  25.   listItem.dataset.id = product.id;
  26.   // 创建产品名称
  27.   const nameElement = document.createElement('h3');
  28.   nameElement.className = 'product-name';
  29.   nameElement.textContent = product.name;
  30.   listItem.appendChild(nameElement);
  31.   // 创建产品价格
  32.   const priceElement = document.createElement('p');
  33.   priceElement.className = 'product-price';
  34.   priceElement.textContent = `价格: ¥${product.price}`;
  35.   listItem.appendChild(priceElement);
  36.   // 创建添加到购物车按钮
  37.   const addButton = document.createElement('button');
  38.   addButton.className = 'add-to-cart';
  39.   addButton.textContent = '添加到购物车';
  40.   
  41.   // 添加点击事件
  42.   addButton.addEventListener('click', function() {
  43.     alert(`已添加 ${product.name} 到购物车`);
  44.   });
  45.   
  46.   listItem.appendChild(addButton);
  47.   // 将列表项添加到文档片段
  48.   fragment.appendChild(listItem);
  49. });
  50. // 将所有列表项一次性添加到列表
  51. list.appendChild(fragment);
  52. // 将列表添加到容器
  53. productList.appendChild(list);
  54. // 将整个产品列表添加到页面
  55. document.body.appendChild(productList);
  56. // 添加样式
  57. const style = document.createElement('style');
  58. style.textContent = `
  59.   .product-list {
  60.     font-family: Arial, sans-serif;
  61.     max-width: 600px;
  62.     margin: 0 auto;
  63.     padding: 20px;
  64.   }
  65.   .products {
  66.     list-style: none;
  67.     padding: 0;
  68.   }
  69.   .product-item {
  70.     border: 1px solid #ddd;
  71.     border-radius: 5px;
  72.     padding: 15px;
  73.     margin-bottom: 10px;
  74.     background-color: #f9f9f9;
  75.   }
  76.   .product-name {
  77.     margin-top: 0;
  78.     color: #333;
  79.   }
  80.   .product-price {
  81.     color: #e44d26;
  82.     font-weight: bold;
  83.   }
  84.   .add-to-cart {
  85.     background-color: #4CAF50;
  86.     color: white;
  87.     border: none;
  88.     padding: 8px 12px;
  89.     border-radius: 4px;
  90.     cursor: pointer;
  91.   }
  92.   .add-to-cart:hover {
  93.     background-color: #45a049;
  94.   }
  95. `;
  96. document.head.appendChild(style);
复制代码

表单动态生成

动态生成表单是另一个常见的需求,特别是在需要根据用户选择或后端数据动态调整表单字段时。
  1. // 表单字段配置
  2. const formFields = [
  3.   {
  4.     type: 'text',
  5.     name: 'username',
  6.     label: '用户名',
  7.     required: true,
  8.     placeholder: '请输入用户名'
  9.   },
  10.   {
  11.     type: 'email',
  12.     name: 'email',
  13.     label: '电子邮箱',
  14.     required: true,
  15.     placeholder: '请输入电子邮箱'
  16.   },
  17.   {
  18.     type: 'password',
  19.     name: 'password',
  20.     label: '密码',
  21.     required: true,
  22.     placeholder: '请输入密码'
  23.   },
  24.   {
  25.     type: 'select',
  26.     name: 'country',
  27.     label: '国家',
  28.     options: [
  29.       { value: '', text: '请选择国家' },
  30.       { value: 'cn', text: '中国' },
  31.       { value: 'us', text: '美国' },
  32.       { value: 'uk', text: '英国' },
  33.       { value: 'jp', text: '日本' }
  34.     ],
  35.     required: true
  36.   },
  37.   {
  38.     type: 'checkbox',
  39.     name: 'interests',
  40.     label: '兴趣爱好',
  41.     options: [
  42.       { value: 'sports', text: '运动' },
  43.       { value: 'music', text: '音乐' },
  44.       { value: 'reading', text: '阅读' },
  45.       { value: 'travel', text: '旅行' }
  46.     ]
  47.   },
  48.   {
  49.     type: 'radio',
  50.     name: 'gender',
  51.     label: '性别',
  52.     options: [
  53.       { value: 'male', text: '男' },
  54.       { value: 'female', text: '女' },
  55.       { value: 'other', text: '其他' }
  56.     ],
  57.     required: true
  58.   },
  59.   {
  60.     type: 'textarea',
  61.     name: 'bio',
  62.     label: '个人简介',
  63.     placeholder: '请简单介绍一下自己',
  64.     rows: 4
  65.   }
  66. ];
  67. // 创建表单容器
  68. const formContainer = document.createElement('div');
  69. formContainer.className = 'form-container';
  70. // 创建表单标题
  71. const formTitle = document.createElement('h2');
  72. formTitle.textContent = '用户注册表单';
  73. formContainer.appendChild(formTitle);
  74. // 创建表单元素
  75. const form = document.createElement('form');
  76. form.id = 'registration-form';
  77. // 遍历表单字段配置创建表单控件
  78. formFields.forEach(field => {
  79.   // 创建字段容器
  80.   const fieldContainer = document.createElement('div');
  81.   fieldContainer.className = 'form-group';
  82.   // 创建标签
  83.   const label = document.createElement('label');
  84.   label.textContent = field.label;
  85.   
  86.   // 如果是必填字段,添加必填标记
  87.   if (field.required) {
  88.     const requiredMarker = document.createElement('span');
  89.     requiredMarker.className = 'required';
  90.     requiredMarker.textContent = ' *';
  91.     label.appendChild(requiredMarker);
  92.   }
  93.   
  94.   fieldContainer.appendChild(label);
  95.   // 根据字段类型创建不同的输入控件
  96.   let input;
  97.   
  98.   switch (field.type) {
  99.     case 'text':
  100.     case 'email':
  101.     case 'password':
  102.       input = document.createElement('input');
  103.       input.type = field.type;
  104.       input.name = field.name;
  105.       input.id = field.name;
  106.       
  107.       if (field.placeholder) {
  108.         input.placeholder = field.placeholder;
  109.       }
  110.       
  111.       if (field.required) {
  112.         input.required = true;
  113.       }
  114.       
  115.       fieldContainer.appendChild(input);
  116.       break;
  117.       
  118.     case 'textarea':
  119.       input = document.createElement('textarea');
  120.       input.name = field.name;
  121.       input.id = field.name;
  122.       
  123.       if (field.placeholder) {
  124.         input.placeholder = field.placeholder;
  125.       }
  126.       
  127.       if (field.rows) {
  128.         input.rows = field.rows;
  129.       }
  130.       
  131.       fieldContainer.appendChild(input);
  132.       break;
  133.       
  134.     case 'select':
  135.       input = document.createElement('select');
  136.       input.name = field.name;
  137.       input.id = field.name;
  138.       
  139.       if (field.required) {
  140.         input.required = true;
  141.       }
  142.       
  143.       // 添加选项
  144.       field.options.forEach(option => {
  145.         const optionElement = document.createElement('option');
  146.         optionElement.value = option.value;
  147.         optionElement.textContent = option.text;
  148.         input.appendChild(optionElement);
  149.       });
  150.       
  151.       fieldContainer.appendChild(input);
  152.       break;
  153.       
  154.     case 'checkbox':
  155.     case 'radio':
  156.       // 创建选项容器
  157.       const optionsContainer = document.createElement('div');
  158.       optionsContainer.className = 'options-container';
  159.       
  160.       // 创建每个选项
  161.       field.options.forEach(option => {
  162.         const optionContainer = document.createElement('div');
  163.         optionContainer.className = 'option-item';
  164.         
  165.         const optionInput = document.createElement('input');
  166.         optionInput.type = field.type;
  167.         optionInput.name = field.name;
  168.         optionInput.id = `${field.name}-${option.value}`;
  169.         optionInput.value = option.value;
  170.         
  171.         const optionLabel = document.createElement('label');
  172.         optionLabel.htmlFor = `${field.name}-${option.value}`;
  173.         optionLabel.textContent = option.text;
  174.         
  175.         optionContainer.appendChild(optionInput);
  176.         optionContainer.appendChild(optionLabel);
  177.         optionsContainer.appendChild(optionContainer);
  178.       });
  179.       
  180.       fieldContainer.appendChild(optionsContainer);
  181.       break;
  182.   }
  183.   // 添加错误消息容器
  184.   const errorContainer = document.createElement('div');
  185.   errorContainer.className = 'error-message';
  186.   errorContainer.id = `${field.name}-error`;
  187.   fieldContainer.appendChild(errorContainer);
  188.   form.appendChild(fieldContainer);
  189. });
  190. // 创建提交按钮
  191. const submitButton = document.createElement('button');
  192. submitButton.type = 'submit';
  193. submitButton.textContent = '提交注册';
  194. form.appendChild(submitButton);
  195. // 添加表单到容器
  196. formContainer.appendChild(form);
  197. // 添加到DOM
  198. document.body.appendChild(formContainer);
  199. // 添加表单验证和提交处理
  200. form.addEventListener('submit', function(event) {
  201.   event.preventDefault();
  202.   
  203.   let isValid = true;
  204.   
  205.   // 验证每个字段
  206.   formFields.forEach(field => {
  207.     if (field.required) {
  208.       let fieldValue;
  209.       const errorContainer = document.getElementById(`${field.name}-error`);
  210.       
  211.       if (field.type === 'checkbox' || field.type === 'radio') {
  212.         // 对于复选框和单选按钮,检查是否有选中项
  213.         const inputs = form.querySelectorAll(`input[name="${field.name}"]:checked`);
  214.         fieldValue = inputs.length > 0;
  215.         
  216.         if (!fieldValue) {
  217.           errorContainer.textContent = `请选择${field.label}`;
  218.           isValid = false;
  219.         } else {
  220.           errorContainer.textContent = '';
  221.         }
  222.       } else {
  223.         // 对于其他输入类型,检查值是否为空
  224.         const input = form.querySelector(`[name="${field.name}"]`);
  225.         fieldValue = input.value.trim();
  226.         
  227.         if (!fieldValue) {
  228.           errorContainer.textContent = `${field.label}不能为空`;
  229.           isValid = false;
  230.         } else {
  231.           // 特定验证
  232.           if (field.type === 'email' && !validateEmail(fieldValue)) {
  233.             errorContainer.textContent = '请输入有效的电子邮箱地址';
  234.             isValid = false;
  235.           } else {
  236.             errorContainer.textContent = '';
  237.           }
  238.         }
  239.       }
  240.     }
  241.   });
  242.   
  243.   if (isValid) {
  244.     // 收集表单数据
  245.     const formData = new FormData(form);
  246.     const data = {};
  247.    
  248.     for (let [key, value] of formData.entries()) {
  249.       if (data[key]) {
  250.         // 如果键已存在,转换为数组
  251.         if (!Array.isArray(data[key])) {
  252.           data[key] = [data[key]];
  253.         }
  254.         data[key].push(value);
  255.       } else {
  256.         data[key] = value;
  257.       }
  258.     }
  259.    
  260.     // 显示成功消息
  261.     const successMessage = document.createElement('div');
  262.     successMessage.className = 'success-message';
  263.     successMessage.textContent = '表单提交成功!数据已收集。';
  264.     formContainer.appendChild(successMessage);
  265.    
  266.     // 输出收集的数据(实际应用中,这里可能是AJAX请求)
  267.     console.log('收集的表单数据:', data);
  268.    
  269.     // 重置表单
  270.     form.reset();
  271.    
  272.     // 3秒后移除成功消息
  273.     setTimeout(() => {
  274.       successMessage.remove();
  275.     }, 3000);
  276.   }
  277. });
  278. // 邮箱验证函数
  279. function validateEmail(email) {
  280.   const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  281.   return re.test(email);
  282. }
  283. // 添加样式
  284. const formStyle = document.createElement('style');
  285. formStyle.textContent = `
  286.   .form-container {
  287.     max-width: 600px;
  288.     margin: 0 auto;
  289.     padding: 20px;
  290.     font-family: Arial, sans-serif;
  291.   }
  292.   .form-group {
  293.     margin-bottom: 15px;
  294.   }
  295.   label {
  296.     display: block;
  297.     margin-bottom: 5px;
  298.     font-weight: bold;
  299.   }
  300.   .required {
  301.     color: red;
  302.   }
  303.   input[type="text"],
  304.   input[type="email"],
  305.   input[type="password"],
  306.   select,
  307.   textarea {
  308.     width: 100%;
  309.     padding: 8px;
  310.     border: 1px solid #ddd;
  311.     border-radius: 4px;
  312.     box-sizing: border-box;
  313.   }
  314.   .options-container {
  315.     margin-top: 5px;
  316.   }
  317.   .option-item {
  318.     margin-bottom: 5px;
  319.   }
  320.   input[type="checkbox"],
  321.   input[type="radio"] {
  322.     margin-right: 5px;
  323.   }
  324.   button {
  325.     background-color: #4CAF50;
  326.     color: white;
  327.     border: none;
  328.     padding: 10px 15px;
  329.     border-radius: 4px;
  330.     cursor: pointer;
  331.     font-size: 16px;
  332.   }
  333.   button:hover {
  334.     background-color: #45a049;
  335.   }
  336.   .error-message {
  337.     color: red;
  338.     font-size: 14px;
  339.     margin-top: 5px;
  340.   }
  341.   .success-message {
  342.     background-color: #dff0d8;
  343.     color: #3c763d;
  344.     padding: 10px;
  345.     border-radius: 4px;
  346.     margin-top: 20px;
  347.   }
  348. `;
  349. document.head.appendChild(formStyle);
复制代码

模态框动态创建

模态框(Modal)是现代Web应用中常见的UI组件,用于显示重要信息或需要用户交互的内容。下面是一个动态创建模态框的完整示例。
  1. // 创建模态框的函数
  2. function createModal(options) {
  3.   // 设置默认选项
  4.   const defaultOptions = {
  5.     title: '提示',
  6.     content: '',
  7.     showFooter: true,
  8.     showCloseButton: true,
  9.     closeOnOverlayClick: true,
  10.     buttons: [
  11.       {
  12.         text: '确定',
  13.         class: 'btn-primary',
  14.         action: function() {
  15.           closeModal(modalElement);
  16.         }
  17.       }
  18.     ]
  19.   };
  20.   
  21.   // 合并选项
  22.   options = Object.assign({}, defaultOptions, options);
  23.   
  24.   // 创建模态框元素
  25.   const modalElement = document.createElement('div');
  26.   modalElement.className = 'modal';
  27.   modalElement.style.display = 'none';
  28.   
  29.   // 创建遮罩层
  30.   const overlay = document.createElement('div');
  31.   overlay.className = 'modal-overlay';
  32.   
  33.   // 创建模态框内容容器
  34.   const modalContainer = document.createElement('div');
  35.   modalContainer.className = 'modal-container';
  36.   
  37.   // 创建头部
  38.   const header = document.createElement('div');
  39.   header.className = 'modal-header';
  40.   
  41.   const title = document.createElement('h3');
  42.   title.className = 'modal-title';
  43.   title.textContent = options.title;
  44.   header.appendChild(title);
  45.   
  46.   // 添加关闭按钮
  47.   if (options.showCloseButton) {
  48.     const closeButton = document.createElement('button');
  49.     closeButton.className = 'modal-close';
  50.     closeButton.innerHTML = '&times;';
  51.     closeButton.addEventListener('click', function() {
  52.       closeModal(modalElement);
  53.     });
  54.     header.appendChild(closeButton);
  55.   }
  56.   
  57.   modalContainer.appendChild(header);
  58.   
  59.   // 创建内容区域
  60.   const content = document.createElement('div');
  61.   content.className = 'modal-content';
  62.   
  63.   // 如果内容是字符串,直接设置
  64.   if (typeof options.content === 'string') {
  65.     content.innerHTML = options.content;
  66.   }
  67.   // 如果内容是DOM元素,添加到内容区域
  68.   else if (options.content instanceof HTMLElement) {
  69.     content.appendChild(options.content);
  70.   }
  71.   
  72.   modalContainer.appendChild(content);
  73.   
  74.   // 创建底部按钮区域
  75.   if (options.showFooter) {
  76.     const footer = document.createElement('div');
  77.     footer.className = 'modal-footer';
  78.    
  79.     // 添加按钮
  80.     options.buttons.forEach(button => {
  81.       const buttonElement = document.createElement('button');
  82.       buttonElement.className = button.class || 'btn-default';
  83.       buttonElement.textContent = button.text;
  84.       
  85.       buttonElement.addEventListener('click', function() {
  86.         if (button.action && typeof button.action === 'function') {
  87.           button.action();
  88.         }
  89.       });
  90.       
  91.       footer.appendChild(buttonElement);
  92.     });
  93.    
  94.     modalContainer.appendChild(footer);
  95.   }
  96.   
  97.   // 组装模态框
  98.   modalElement.appendChild(overlay);
  99.   modalElement.appendChild(modalContainer);
  100.   
  101.   // 点击遮罩层关闭模态框
  102.   if (options.closeOnOverlayClick) {
  103.     overlay.addEventListener('click', function() {
  104.       closeModal(modalElement);
  105.     });
  106.   }
  107.   
  108.   // 阻止点击内容区域时关闭模态框
  109.   modalContainer.addEventListener('click', function(event) {
  110.     event.stopPropagation();
  111.   });
  112.   
  113.   // 添加到DOM
  114.   document.body.appendChild(modalElement);
  115.   
  116.   return modalElement;
  117. }
  118. // 打开模态框的函数
  119. function openModal(modalElement) {
  120.   modalElement.style.display = 'block';
  121.   
  122.   // 添加动画类
  123.   setTimeout(() => {
  124.     modalElement.classList.add('show');
  125.   }, 10);
  126.   
  127.   // 禁止背景滚动
  128.   document.body.style.overflow = 'hidden';
  129. }
  130. // 关闭模态框的函数
  131. function closeModal(modalElement) {
  132.   modalElement.classList.remove('show');
  133.   
  134.   // 动画结束后隐藏模态框
  135.   setTimeout(() => {
  136.     modalElement.style.display = 'none';
  137.    
  138.     // 恢复背景滚动
  139.     document.body.style.overflow = '';
  140.   }, 300);
  141. }
  142. // 创建示例按钮
  143. const showModalButton = document.createElement('button');
  144. showModalButton.textContent = '显示模态框';
  145. showModalButton.className = 'btn-demo';
  146. showModalButton.addEventListener('click', function() {
  147.   // 创建模态框内容
  148.   const modalContent = document.createElement('div');
  149.   modalContent.innerHTML = `
  150.     <p>这是一个动态创建的模态框示例。</p>
  151.     <p>您可以通过JavaScript动态创建各种类型的模态框,包括:</p>
  152.     <ul>
  153.       <li>提示信息</li>
  154.       <li>确认对话框</li>
  155.       <li>表单输入</li>
  156.       <li>图片预览</li>
  157.       <li>等等...</li>
  158.     </ul>
  159.   `;
  160.   
  161.   // 创建模态框
  162.   const modal = createModal({
  163.     title: '模态框示例',
  164.     content: modalContent,
  165.     buttons: [
  166.       {
  167.         text: '取消',
  168.         class: 'btn-secondary',
  169.         action: function() {
  170.           closeModal(modal);
  171.         }
  172.       },
  173.       {
  174.         text: '确定',
  175.         class: 'btn-primary',
  176.         action: function() {
  177.           alert('您点击了确定按钮!');
  178.           closeModal(modal);
  179.         }
  180.       }
  181.     ]
  182.   });
  183.   
  184.   // 显示模态框
  185.   openModal(modal);
  186. });
  187. document.body.appendChild(showModalButton);
  188. // 创建表单模态框示例
  189. const showFormModalButton = document.createElement('button');
  190. showFormModalButton.textContent = '显示表单模态框';
  191. showFormModalButton.className = 'btn-demo';
  192. showFormModalButton.addEventListener('click', function() {
  193.   // 创建表单
  194.   const form = document.createElement('form');
  195.   form.id = 'modal-form';
  196.   
  197.   // 用户名字段
  198.   const usernameGroup = document.createElement('div');
  199.   usernameGroup.className = 'form-group';
  200.   
  201.   const usernameLabel = document.createElement('label');
  202.   usernameLabel.textContent = '用户名';
  203.   usernameGroup.appendChild(usernameLabel);
  204.   
  205.   const usernameInput = document.createElement('input');
  206.   usernameInput.type = 'text';
  207.   usernameInput.name = 'username';
  208.   usernameInput.required = true;
  209.   usernameGroup.appendChild(usernameInput);
  210.   
  211.   form.appendChild(usernameGroup);
  212.   
  213.   // 邮箱字段
  214.   const emailGroup = document.createElement('div');
  215.   emailGroup.className = 'form-group';
  216.   
  217.   const emailLabel = document.createElement('label');
  218.   emailLabel.textContent = '邮箱';
  219.   emailGroup.appendChild(emailLabel);
  220.   
  221.   const emailInput = document.createElement('input');
  222.   emailInput.type = 'email';
  223.   emailInput.name = 'email';
  224.   emailInput.required = true;
  225.   emailGroup.appendChild(emailInput);
  226.   
  227.   form.appendChild(emailGroup);
  228.   
  229.   // 创建模态框
  230.   const modal = createModal({
  231.     title: '用户信息',
  232.     content: form,
  233.     buttons: [
  234.       {
  235.         text: '取消',
  236.         class: 'btn-secondary',
  237.         action: function() {
  238.           closeModal(modal);
  239.         }
  240.       },
  241.       {
  242.         text: '提交',
  243.         class: 'btn-primary',
  244.         action: function() {
  245.           // 验证表单
  246.           if (form.checkValidity()) {
  247.             const formData = new FormData(form);
  248.             const data = Object.fromEntries(formData);
  249.             console.log('表单数据:', data);
  250.             alert('表单提交成功!');
  251.             closeModal(modal);
  252.           } else {
  253.             // 触发浏览器验证
  254.             form.reportValidity();
  255.           }
  256.         }
  257.       }
  258.     ]
  259.   });
  260.   
  261.   // 显示模态框
  262.   openModal(modal);
  263. });
  264. document.body.appendChild(showFormModalButton);
  265. // 添加样式
  266. const modalStyle = document.createElement('style');
  267. modalStyle.textContent = `
  268.   .btn-demo {
  269.     margin: 10px;
  270.     padding: 10px 15px;
  271.     background-color: #4CAF50;
  272.     color: white;
  273.     border: none;
  274.     border-radius: 4px;
  275.     cursor: pointer;
  276.   }
  277.   .btn-demo:hover {
  278.     background-color: #45a049;
  279.   }
  280.   .modal {
  281.     position: fixed;
  282.     top: 0;
  283.     left: 0;
  284.     width: 100%;
  285.     height: 100%;
  286.     z-index: 1000;
  287.     opacity: 0;
  288.     transition: opacity 0.3s ease;
  289.   }
  290.   .modal.show {
  291.     opacity: 1;
  292.   }
  293.   .modal-overlay {
  294.     position: absolute;
  295.     top: 0;
  296.     left: 0;
  297.     width: 100%;
  298.     height: 100%;
  299.     background-color: rgba(0, 0, 0, 0.5);
  300.   }
  301.   .modal-container {
  302.     position: relative;
  303.     background-color: white;
  304.     width: 90%;
  305.     max-width: 600px;
  306.     margin: 50px auto;
  307.     border-radius: 5px;
  308.     box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
  309.     transform: translateY(-20px);
  310.     transition: transform 0.3s ease;
  311.   }
  312.   .modal.show .modal-container {
  313.     transform: translateY(0);
  314.   }
  315.   .modal-header {
  316.     padding: 15px 20px;
  317.     border-bottom: 1px solid #eee;
  318.     display: flex;
  319.     justify-content: space-between;
  320.     align-items: center;
  321.   }
  322.   .modal-title {
  323.     margin: 0;
  324.     font-size: 18px;
  325.   }
  326.   .modal-close {
  327.     background: none;
  328.     border: none;
  329.     font-size: 24px;
  330.     cursor: pointer;
  331.     color: #999;
  332.   }
  333.   .modal-close:hover {
  334.     color: #333;
  335.   }
  336.   .modal-content {
  337.     padding: 20px;
  338.   }
  339.   .modal-footer {
  340.     padding: 15px 20px;
  341.     border-top: 1px solid #eee;
  342.     display: flex;
  343.     justify-content: flex-end;
  344.     gap: 10px;
  345.   }
  346.   .btn-primary {
  347.     background-color: #007bff;
  348.     color: white;
  349.     border: none;
  350.     padding: 8px 16px;
  351.     border-radius: 4px;
  352.     cursor: pointer;
  353.   }
  354.   .btn-primary:hover {
  355.     background-color: #0069d9;
  356.   }
  357.   .btn-secondary {
  358.     background-color: #6c757d;
  359.     color: white;
  360.     border: none;
  361.     padding: 8px 16px;
  362.     border-radius: 4px;
  363.     cursor: pointer;
  364.   }
  365.   .btn-secondary:hover {
  366.     background-color: #5a6268;
  367.   }
  368.   .form-group {
  369.     margin-bottom: 15px;
  370.   }
  371.   .form-group label {
  372.     display: block;
  373.     margin-bottom: 5px;
  374.   }
  375.   .form-group input {
  376.     width: 100%;
  377.     padding: 8px;
  378.     border: 1px solid #ddd;
  379.     border-radius: 4px;
  380.     box-sizing: border-box;
  381.   }
  382. `;
  383. document.head.appendChild(modalStyle);
复制代码

无限滚动加载

无限滚动是一种常见的网页内容加载方式,当用户滚动到页面底部时,自动加载更多内容。下面是一个实现无限滚动加载的完整示例。
  1. // 模拟数据生成函数
  2. function generateItems(start, count) {
  3.   const items = [];
  4.   for (let i = start; i < start + count; i++) {
  5.     items.push({
  6.       id: i,
  7.       title: `项目 ${i}`,
  8.       content: `这是项目 ${i} 的内容描述。这里可以包含更多的文本内容来模拟真实的项目卡片。`,
  9.       image: `https://picsum.photos/seed/item${i}/300/200.jpg`
  10.     });
  11.   }
  12.   return items;
  13. }
  14. // 创建无限滚动容器
  15. const container = document.createElement('div');
  16. container.className = 'infinite-scroll-container';
  17. // 创建标题
  18. const title = document.createElement('h2');
  19. title.textContent = '无限滚动加载示例';
  20. container.appendChild(title);
  21. // 创建内容区域
  22. const contentArea = document.createElement('div');
  23. contentArea.className = 'content-area';
  24. // 创建加载指示器
  25. const loadingIndicator = document.createElement('div');
  26. loadingIndicator.className = 'loading-indicator';
  27. loadingIndicator.textContent = '加载中...';
  28. loadingIndicator.style.display = 'none';
  29. // 添加到容器
  30. container.appendChild(contentArea);
  31. container.appendChild(loadingIndicator);
  32. // 添加到DOM
  33. document.body.appendChild(container);
  34. // 状态变量
  35. let page = 1;
  36. const itemsPerPage = 10;
  37. let isLoading = false;
  38. let hasMoreItems = true;
  39. // 创建项目卡片函数
  40. function createItemCard(item) {
  41.   const card = document.createElement('div');
  42.   card.className = 'item-card';
  43.   
  44.   const image = document.createElement('img');
  45.   image.src = item.image;
  46.   image.alt = item.title;
  47.   card.appendChild(image);
  48.   
  49.   const cardContent = document.createElement('div');
  50.   cardContent.className = 'card-content';
  51.   
  52.   const cardTitle = document.createElement('h3');
  53.   cardTitle.textContent = item.title;
  54.   cardContent.appendChild(cardTitle);
  55.   
  56.   const cardText = document.createElement('p');
  57.   cardText.textContent = item.content;
  58.   cardContent.appendChild(cardText);
  59.   
  60.   card.appendChild(cardContent);
  61.   
  62.   return card;
  63. }
  64. // 加载更多项目函数
  65. function loadMoreItems() {
  66.   if (isLoading || !hasMoreItems) return;
  67.   
  68.   isLoading = true;
  69.   loadingIndicator.style.display = 'block';
  70.   
  71.   // 模拟网络请求延迟
  72.   setTimeout(() => {
  73.     // 生成新项目
  74.     const newItems = generateItems((page - 1) * itemsPerPage + 1, itemsPerPage);
  75.    
  76.     // 创建文档片段以优化性能
  77.     const fragment = document.createDocumentFragment();
  78.    
  79.     // 添加新项目到内容区域
  80.     newItems.forEach(item => {
  81.       const card = createItemCard(item);
  82.       fragment.appendChild(card);
  83.     });
  84.    
  85.     contentArea.appendChild(fragment);
  86.    
  87.     // 更新状态
  88.     page++;
  89.     isLoading = false;
  90.     loadingIndicator.style.display = 'none';
  91.    
  92.     // 模拟数据有限(最多50个项目)
  93.     if (page * itemsPerPage > 50) {
  94.       hasMoreItems = false;
  95.       
  96.       // 显示没有更多内容的消息
  97.       const endMessage = document.createElement('div');
  98.       endMessage.className = 'end-message';
  99.       endMessage.textContent = '没有更多内容了';
  100.       container.appendChild(endMessage);
  101.     }
  102.   }, 1000); // 模拟1秒的加载延迟
  103. }
  104. // 初始加载
  105. loadMoreItems();
  106. // 滚动事件处理函数
  107. function handleScroll() {
  108.   // 计算滚动位置
  109.   const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  110.   const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
  111.   const clientHeight = document.documentElement.clientHeight || window.innerHeight;
  112.   
  113.   // 当滚动到距离底部200px时加载更多内容
  114.   if (scrollTop + clientHeight >= scrollHeight - 200) {
  115.     loadMoreItems();
  116.   }
  117. }
  118. // 添加滚动事件监听器
  119. window.addEventListener('scroll', handleScroll);
  120. // 添加样式
  121. const scrollStyle = document.createElement('style');
  122. scrollStyle.textContent = `
  123.   .infinite-scroll-container {
  124.     max-width: 800px;
  125.     margin: 0 auto;
  126.     padding: 20px;
  127.     font-family: Arial, sans-serif;
  128.   }
  129.   .content-area {
  130.     display: grid;
  131.     grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  132.     gap: 20px;
  133.   }
  134.   .item-card {
  135.     border: 1px solid #ddd;
  136.     border-radius: 8px;
  137.     overflow: hidden;
  138.     box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  139.     transition: transform 0.3s ease;
  140.   }
  141.   .item-card:hover {
  142.     transform: translateY(-5px);
  143.     box-shadow: 0 5px 15px rgba(0,0,0,0.2);
  144.   }
  145.   .item-card img {
  146.     width: 100%;
  147.     height: 200px;
  148.     object-fit: cover;
  149.   }
  150.   .card-content {
  151.     padding: 15px;
  152.   }
  153.   .card-content h3 {
  154.     margin-top: 0;
  155.     color: #333;
  156.   }
  157.   .card-content p {
  158.     color: #666;
  159.     line-height: 1.5;
  160.   }
  161.   .loading-indicator {
  162.     text-align: center;
  163.     padding: 20px;
  164.     color: #666;
  165.     font-style: italic;
  166.   }
  167.   .end-message {
  168.     text-align: center;
  169.     padding: 20px;
  170.     color: #999;
  171.     font-style: italic;
  172.   }
  173. `;
  174. document.head.appendChild(scrollStyle);
复制代码

性能优化

DocumentFragment的使用

DocumentFragment是一个轻量级的DOM节点,可以包含其他节点,但不会成为实际DOM树的一部分。当需要添加多个节点到DOM时,使用DocumentFragment可以显著提高性能,因为它只会触发一次重排。
  1. // 不使用DocumentFragment的方式(性能较差)
  2. function addItemsWithoutFragment(container, items) {
  3.   items.forEach(item => {
  4.     const li = document.createElement('li');
  5.     li.textContent = item;
  6.     container.appendChild(li); // 每次appendChild都会触发重排
  7.   });
  8. }
  9. // 使用DocumentFragment的方式(性能更好)
  10. function addItemsWithFragment(container, items) {
  11.   const fragment = document.createDocumentFragment();
  12.   
  13.   items.forEach(item => {
  14.     const li = document.createElement('li');
  15.     li.textContent = item;
  16.     fragment.appendChild(li); // 在内存中操作,不会触发重排
  17.   });
  18.   
  19.   container.appendChild(fragment); // 只触发一次重排
  20. }
  21. // 性能测试
  22. function testPerformance() {
  23.   const container1 = document.createElement('ul');
  24.   document.body.appendChild(container1);
  25.   
  26.   const container2 = document.createElement('ul');
  27.   document.body.appendChild(container2);
  28.   
  29.   const items = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);
  30.   
  31.   // 测试不使用DocumentFragment
  32.   console.time('Without Fragment');
  33.   addItemsWithoutFragment(container1, items);
  34.   console.timeEnd('Without Fragment');
  35.   
  36.   // 测试使用DocumentFragment
  37.   console.time('With Fragment');
  38.   addItemsWithFragment(container2, items);
  39.   console.timeEnd('With Fragment');
  40. }
  41. // 运行测试
  42. testPerformance();
复制代码

批量操作优化

当需要对DOM进行多次修改时,尽量将这些操作批量进行,以减少重排和重绘的次数。
  1. // 不好的做法 - 多次单独操作
  2. function badPractice() {
  3.   const container = document.getElementById('container');
  4.   
  5.   // 多次修改样式,每次都会触发重排
  6.   container.style.padding = '10px';
  7.   container.style.margin = '20px';
  8.   container.style.border = '1px solid #ccc';
  9.   
  10.   // 多次修改类名,每次都会触发重排
  11.   container.classList.add('active');
  12.   container.classList.remove('inactive');
  13.   container.classList.add('highlight');
  14.   
  15.   // 多次修改内容,每次都会触发重排
  16.   container.innerHTML = '<p>第一步</p>';
  17.   container.innerHTML += '<p>第二步</p>';
  18.   container.innerHTML += '<p>第三步</p>';
  19. }
  20. // 好的做法 - 批量操作
  21. function goodPractice() {
  22.   const container = document.getElementById('container');
  23.   
  24.   // 使用CSS类一次性应用多个样式
  25.   container.className = 'styled-container active highlight';
  26.   
  27.   // 一次性构建完整内容
  28.   const content = `
  29.     <p>第一步</p>
  30.     <p>第二步</p>
  31.     <p>第三步</p>
  32.   `;
  33.   container.innerHTML = content;
  34.   
  35.   // 或者使用DocumentFragment
  36.   const fragment = document.createDocumentFragment();
  37.   
  38.   const p1 = document.createElement('p');
  39.   p1.textContent = '第一步';
  40.   fragment.appendChild(p1);
  41.   
  42.   const p2 = document.createElement('p');
  43.   p2.textContent = '第二步';
  44.   fragment.appendChild(p2);
  45.   
  46.   const p3 = document.createElement('p');
  47.   p3.textContent = '第三步';
  48.   fragment.appendChild(p3);
  49.   
  50.   container.appendChild(fragment);
  51. }
  52. // 添加样式
  53. const style = document.createElement('style');
  54. style.textContent = `
  55.   .styled-container {
  56.     padding: 10px;
  57.     margin: 20px;
  58.     border: 1px solid #ccc;
  59.   }
  60.   .active {
  61.     background-color: #f0f0f0;
  62.   }
  63.   .highlight {
  64.     box-shadow: 0 0 10px rgba(0,0,0,0.1);
  65.   }
  66. `;
  67. document.head.appendChild(style);
复制代码

重排与重绘优化

理解浏览器的重排(reflow)和重绘(repaint)机制对于优化DOM操作性能至关重要。重排是指浏览器重新计算元素的几何属性(位置、大小等),而重绘是指浏览器重新绘制元素的视觉效果。重排比重绘更消耗性能。
  1. // 创建测试容器
  2. const testContainer = document.createElement('div');
  3. testContainer.id = 'test-container';
  4. testContainer.style.padding = '20px';
  5. document.body.appendChild(testContainer);
  6. // 创建测试元素
  7. function createTestElements(count) {
  8.   const fragment = document.createDocumentFragment();
  9.   
  10.   for (let i = 0; i < count; i++) {
  11.     const div = document.createElement('div');
  12.     div.className = 'test-item';
  13.     div.textContent = `Item ${i}`;
  14.     fragment.appendChild(div);
  15.   }
  16.   
  17.   testContainer.appendChild(fragment);
  18. }
  19. createTestElements(100);
  20. // 不好的做法 - 导致多次重排
  21. function badReflowExample() {
  22.   const items = document.querySelectorAll('.test-item');
  23.   
  24.   items.forEach(item => {
  25.     // 每次修改样式都会触发重排
  26.     item.style.width = '200px';
  27.     item.style.height = '50px';
  28.     item.style.margin = '5px';
  29.     item.style.backgroundColor = '#f0f0f0';
  30.   });
  31. }
  32. // 好的做法 - 减少重排次数
  33. function goodReflowExample() {
  34.   const items = document.querySelectorAll('.test-item');
  35.   
  36.   // 方法1: 使用CSS类一次性应用多个样式
  37.   items.forEach(item => {
  38.     item.className = 'test-item styled';
  39.   });
  40.   
  41.   // 方法2: 批量读取,然后批量写入
  42.   // 先读取所有需要的信息
  43.   const itemData = Array.from(items).map(item => ({
  44.     element: item,
  45.     width: item.offsetWidth,
  46.     height: item.offsetHeight
  47.   }));
  48.   
  49.   // 然后一次性写入所有修改
  50.   itemData.forEach(data => {
  51.     data.element.style.width = `${data.width + 10}px`;
  52.     data.element.style.height = `${data.height + 10}px`;
  53.   });
  54. }
  55. // 方法3: 使用requestAnimationFrame进行优化
  56. function optimizedReflowExample() {
  57.   const items = document.querySelectorAll('.test-item');
  58.   
  59.   requestAnimationFrame(() => {
  60.     items.forEach(item => {
  61.       item.style.width = '210px';
  62.       item.style.height = '60px';
  63.       item.style.margin = '10px';
  64.       item.style.backgroundColor = '#e0e0e0';
  65.     });
  66.   });
  67. }
  68. // 添加样式
  69. const reflowStyle = document.createElement('style');
  70. reflowStyle.textContent = `
  71.   .test-item {
  72.     padding: 10px;
  73.     margin: 5px;
  74.     background-color: #f8f8f8;
  75.     border: 1px solid #ddd;
  76.     transition: all 0.3s ease;
  77.   }
  78.   .styled {
  79.     width: 200px;
  80.     height: 50px;
  81.     margin: 5px;
  82.     background-color: #f0f0f0;
  83.   }
  84. `;
  85. document.head.appendChild(reflowStyle);
  86. // 创建测试按钮
  87. const testButton1 = document.createElement('button');
  88. testButton1.textContent = '不好的做法';
  89. testButton1.addEventListener('click', badReflowExample);
  90. document.body.appendChild(testButton1);
  91. const testButton2 = document.createElement('button');
  92. testButton2.textContent = '好的做法';
  93. testButton2.addEventListener('click', goodReflowExample);
  94. document.body.appendChild(testButton2);
  95. const testButton3 = document.createElement('button');
  96. testButton3.textContent = '优化的做法';
  97. testButton3.addEventListener('click', optimizedReflowExample);
  98. document.body.appendChild(testButton3);
复制代码

最佳实践与注意事项

事件委托

事件委托是一种利用事件冒泡机制的技术,通过在父元素上设置事件监听器来管理其所有子元素的事件。这种方法可以减少事件监听器的数量,提高性能,特别是对于动态添加的元素。
  1. // 创建列表容器
  2. const listContainer = document.createElement('div');
  3. listContainer.className = 'list-container';
  4. // 创建列表标题
  5. const listTitle = document.createElement('h3');
  6. listTitle.textContent = '事件委托示例';
  7. listContainer.appendChild(listTitle);
  8. // 创建列表
  9. const list = document.createElement('ul');
  10. list.className = 'delegation-list';
  11. list.id = 'delegation-list';
  12. // 添加初始列表项
  13. const initialItems = ['项目1', '项目2', '项目3'];
  14. initialItems.forEach(itemText => {
  15.   const li = document.createElement('li');
  16.   li.textContent = itemText;
  17.   li.className = 'list-item';
  18.   list.appendChild(li);
  19. });
  20. // 添加到容器
  21. listContainer.appendChild(list);
  22. // 添加添加项目按钮
  23. const addButton = document.createElement('button');
  24. addButton.textContent = '添加项目';
  25. addButton.className = 'add-button';
  26. listContainer.appendChild(addButton);
  27. // 添加到DOM
  28. document.body.appendChild(listContainer);
  29. // 不好的做法 - 为每个列表项单独添加事件监听器
  30. function badEventDelegation() {
  31.   const items = document.querySelectorAll('.list-item');
  32.   
  33.   items.forEach(item => {
  34.     item.addEventListener('click', function() {
  35.       this.style.backgroundColor = '#e0f7fa';
  36.       console.log('点击了:', this.textContent);
  37.     });
  38.   });
  39.   
  40.   // 这种方式的问题:
  41.   // 1. 每个列表项都有一个事件监听器,消耗内存
  42.   // 2. 动态添加的列表项不会自动获得事件监听器
  43. }
  44. // 好的做法 - 使用事件委托
  45. function goodEventDelegation() {
  46.   // 只在父元素上添加一个事件监听器
  47.   list.addEventListener('click', function(event) {
  48.     // 检查点击的是否是列表项
  49.     if (event.target.classList.contains('list-item')) {
  50.       event.target.style.backgroundColor = '#e0f7fa';
  51.       console.log('点击了:', event.target.textContent);
  52.     }
  53.   });
  54.   
  55.   // 这种方式的优势:
  56.   // 1. 只有一个事件监听器,节省内存
  57.   // 2. 动态添加的列表项自动获得事件处理能力
  58. }
  59. // 添加新项目的函数
  60. function addNewItem() {
  61.   const itemCount = list.querySelectorAll('.list-item').length;
  62.   const newItem = document.createElement('li');
  63.   newItem.textContent = `项目${itemCount + 1}`;
  64.   newItem.className = 'list-item';
  65.   list.appendChild(newItem);
  66. }
  67. // 为添加按钮绑定事件
  68. addButton.addEventListener('click', addNewItem);
  69. // 使用好的做法
  70. goodEventDelegation();
  71. // 添加样式
  72. const delegationStyle = document.createElement('style');
  73. delegationStyle.textContent = `
  74.   .list-container {
  75.     max-width: 600px;
  76.     margin: 20px auto;
  77.     padding: 20px;
  78.     font-family: Arial, sans-serif;
  79.   }
  80.   .delegation-list {
  81.     list-style: none;
  82.     padding: 0;
  83.   }
  84.   .list-item {
  85.     padding: 10px 15px;
  86.     margin-bottom: 5px;
  87.     background-color: #f5f5f5;
  88.     border-radius: 4px;
  89.     cursor: pointer;
  90.     transition: background-color 0.3s ease;
  91.   }
  92.   .list-item:hover {
  93.     background-color: #eeeeee;
  94.   }
  95.   .add-button {
  96.     margin-top: 10px;
  97.     padding: 8px 12px;
  98.     background-color: #4CAF50;
  99.     color: white;
  100.     border: none;
  101.     border-radius: 4px;
  102.     cursor: pointer;
  103.   }
  104.   .add-button:hover {
  105.     background-color: #45a049;
  106.   }
  107. `;
  108. document.head.appendChild(delegationStyle);
复制代码

内存泄漏预防

在动态创建和操作DOM元素时,如果不注意清理,可能会导致内存泄漏。以下是一些预防内存泄漏的最佳实践。
  1. // 创建测试容器
  2. const memoryTestContainer = document.createElement('div');
  3. memoryTestContainer.className = 'memory-test-container';
  4. document.body.appendChild(memoryTestContainer);
  5. // 创建测试按钮
  6. const createButton = document.createElement('button');
  7. createButton.textContent = '创建元素';
  8. createButton.id = 'create-elements';
  9. memoryTestContainer.appendChild(createButton);
  10. const removeButton = document.createElement('button');
  11. removeButton.textContent = '移除元素';
  12. removeButton.id = 'remove-elements';
  13. memoryTestContainer.appendChild(removeButton);
  14. const leakButton = document.createElement('button');
  15. leakButton.textContent = '创建泄漏元素';
  16. leakButton.id = 'create-leak';
  17. memoryTestContainer.appendChild(leakButton);
  18. const cleanButton = document.createElement('button');
  19. cleanButton.textContent = '清理泄漏';
  20. cleanButton.id = 'clean-leak';
  21. memoryTestContainer.appendChild(cleanButton);
  22. // 创建内容区域
  23. const contentArea = document.createElement('div');
  24. contentArea.id = 'memory-content-area';
  25. memoryTestContainer.appendChild(contentArea);
  26. // 存储元素引用的数组
  27. let elements = [];
  28. let leakedElements = [];
  29. let leakedEventListeners = [];
  30. // 正确创建和移除元素
  31. createButton.addEventListener('click', function() {
  32.   // 创建10个新元素
  33.   for (let i = 0; i < 10; i++) {
  34.     const div = document.createElement('div');
  35.     div.className = 'memory-test-item';
  36.     div.textContent = `元素 ${elements.length + 1}`;
  37.    
  38.     // 添加点击事件
  39.     div.addEventListener('click', function() {
  40.       console.log('点击了:', this.textContent);
  41.     });
  42.    
  43.     // 添加到DOM
  44.     contentArea.appendChild(div);
  45.    
  46.     // 保存引用
  47.     elements.push(div);
  48.   }
  49.   
  50.   console.log('已创建', elements.length, '个元素');
  51. });
  52. removeButton.addEventListener('click', function() {
  53.   // 移除所有元素
  54.   elements.forEach(element => {
  55.     // 从DOM中移除
  56.     if (element.parentNode) {
  57.       element.parentNode.removeChild(element);
  58.     }
  59.    
  60.     // 移除事件监听器(如果知道引用)
  61.     // 在实际应用中,应该保存事件监听器的引用以便移除
  62.   });
  63.   
  64.   // 清空数组
  65.   elements = [];
  66.   
  67.   console.log('已移除所有元素');
  68. });
  69. // 创建内存泄漏示例
  70. leakButton.addEventListener('click', function() {
  71.   // 创建元素但保留不必要的引用
  72.   for (let i = 0; i < 10; i++) {
  73.     const div = document.createElement('div');
  74.     div.className = 'memory-leak-item';
  75.     div.textContent = `泄漏元素 ${leakedElements.length + 1}`;
  76.    
  77.     // 添加事件监听器但保留引用
  78.     const clickHandler = function() {
  79.       console.log('点击了泄漏元素:', this.textContent);
  80.     };
  81.    
  82.     div.addEventListener('click', clickHandler);
  83.    
  84.     // 添加到DOM
  85.     contentArea.appendChild(div);
  86.    
  87.     // 保存元素和事件监听器的引用(即使不再需要)
  88.     leakedElements.push(div);
  89.     leakedEventListeners.push({
  90.       element: div,
  91.       handler: clickHandler
  92.     });
  93.   }
  94.   
  95.   console.log('已创建', leakedElements.length, '个泄漏元素');
  96. });
  97. // 清理内存泄漏
  98. cleanButton.addEventListener('click', function() {
  99.   // 移除所有泄漏元素
  100.   leakedElements.forEach(element => {
  101.     // 从DOM中移除
  102.     if (element.parentNode) {
  103.       element.parentNode.removeChild(element);
  104.     }
  105.   });
  106.   
  107.   // 移除所有事件监听器
  108.   leakedEventListeners.forEach(item => {
  109.     item.element.removeEventListener('click', item.handler);
  110.   });
  111.   
  112.   // 清空数组
  113.   leakedElements = [];
  114.   leakedEventListeners = [];
  115.   
  116.   // 强制垃圾回收(仅在开发环境中可用)
  117.   if (window.gc) {
  118.     window.gc();
  119.     console.log('已执行垃圾回收');
  120.   }
  121.   
  122.   console.log('已清理所有泄漏');
  123. });
  124. // 添加样式
  125. const memoryStyle = document.createElement('style');
  126. memoryStyle.textContent = `
  127.   .memory-test-container {
  128.     max-width: 800px;
  129.     margin: 20px auto;
  130.     padding: 20px;
  131.     font-family: Arial, sans-serif;
  132.   }
  133.   button {
  134.     margin-right: 10px;
  135.     margin-bottom: 10px;
  136.     padding: 8px 12px;
  137.     background-color: #4CAF50;
  138.     color: white;
  139.     border: none;
  140.     border-radius: 4px;
  141.     cursor: pointer;
  142.   }
  143.   button:hover {
  144.     background-color: #45a049;
  145.   }
  146.   #memory-content-area {
  147.     margin-top: 20px;
  148.     border: 1px solid #ddd;
  149.     padding: 15px;
  150.     min-height: 100px;
  151.   }
  152.   .memory-test-item, .memory-leak-item {
  153.     padding: 10px;
  154.     margin-bottom: 5px;
  155.     background-color: #f5f5f5;
  156.     border-radius: 4px;
  157.     cursor: pointer;
  158.   }
  159.   .memory-leak-item {
  160.     background-color: #ffebee;
  161.   }
  162. `;
  163. document.head.appendChild(memoryStyle);
复制代码

安全考虑(XSS防御)

当使用DOM操作动态添加内容时,特别是当内容来自用户输入或外部源时,必须考虑XSS(跨站脚本攻击)的防御。
  1. // 创建安全测试容器
  2. const securityContainer = document.createElement('div');
  3. securityContainer.className = 'security-container';
  4. document.body.appendChild(securityContainer);
  5. // 创建标题
  6. const securityTitle = document.createElement('h2');
  7. securityTitle.textContent = 'XSS防御示例';
  8. securityContainer.appendChild(securityTitle);
  9. // 创建输入区域
  10. const inputArea = document.createElement('div');
  11. inputArea.className = 'input-area';
  12. const inputLabel = document.createElement('label');
  13. inputLabel.textContent = '输入内容(尝试输入HTML或JavaScript代码):';
  14. inputArea.appendChild(inputLabel);
  15. const userInput = document.createElement('textarea');
  16. userInput.id = 'user-input';
  17. userInput.rows = 4;
  18. userInput.cols = 50;
  19. userInput.placeholder = '例如: <script>alert("XSS攻击")</script> 或 <img src="x" onerror="alert(\'XSS\')">';
  20. inputArea.appendChild(userInput);
  21. const submitButton = document.createElement('button');
  22. submitButton.textContent = '提交';
  23. submitButton.id = 'submit-input';
  24. inputArea.appendChild(submitButton);
  25. securityContainer.appendChild(inputArea);
  26. // 创建显示区域
  27. const displayArea = document.createElement('div');
  28. displayArea.className = 'display-area';
  29. const unsafeTitle = document.createElement('h3');
  30. unsafeTitle.textContent = '不安全的显示方式:';
  31. displayArea.appendChild(unsafeTitle);
  32. const unsafeDisplay = document.createElement('div');
  33. unsafeDisplay.id = 'unsafe-display';
  34. unsafeDisplay.className = 'display-box';
  35. displayArea.appendChild(unsafeDisplay);
  36. const safeTitle = document.createElement('h3');
  37. safeTitle.textContent = '安全的显示方式:';
  38. displayArea.appendChild(safeTitle);
  39. const safeDisplay = document.createElement('div');
  40. safeDisplay.id = 'safe-display';
  41. safeDisplay.className = 'display-box';
  42. displayArea.appendChild(safeDisplay);
  43. securityContainer.appendChild(displayArea);
  44. // 不安全的显示方式(容易受到XSS攻击)
  45. function displayUnsafe(content) {
  46.   // 直接使用innerHTML,不进行任何转义
  47.   unsafeDisplay.innerHTML = content;
  48. }
  49. // 安全的显示方式(防御XSS攻击)
  50. function displaySafe(content) {
  51.   // 方法1: 使用textContent而不是innerHTML
  52.   safeDisplay.textContent = content;
  53.   
  54.   // 方法2: 如果需要保留一些HTML标签,可以使用安全的HTML转义函数
  55.   // safeDisplay.innerHTML = escapeHtml(content);
  56. }
  57. // HTML转义函数
  58. function escapeHtml(unsafe) {
  59.   return unsafe
  60.     .replace(/&/g, "&amp;")
  61.     .replace(/</g, "&lt;")
  62.     .replace(/>/g, "&gt;")
  63.     .replace(/"/g, "&quot;")
  64.     .replace(/'/g, "&#039;");
  65. }
  66. // 创建DOM元素的安全方式
  67. function createElementSafe(tagName, attributes, content) {
  68.   const element = document.createElement(tagName);
  69.   
  70.   // 安全设置属性
  71.   if (attributes) {
  72.     for (const [key, value] of Object.entries(attributes)) {
  73.       // 对于事件处理程序属性,使用addEventListener而不是直接设置
  74.       if (key.startsWith('on')) {
  75.         const eventName = key.substring(2).toLowerCase();
  76.         if (typeof value === 'function') {
  77.           element.addEventListener(eventName, value);
  78.         }
  79.       } else {
  80.         // 对于其他属性,使用setAttribute
  81.         element.setAttribute(key, value);
  82.       }
  83.     }
  84.   }
  85.   
  86.   // 安全设置内容
  87.   if (content !== undefined && content !== null) {
  88.     if (typeof content === 'string') {
  89.       // 使用textContent而不是innerHTML
  90.       element.textContent = content;
  91.     } else if (content instanceof HTMLElement) {
  92.       element.appendChild(content);
  93.     }
  94.   }
  95.   
  96.   return element;
  97. }
  98. // 提交按钮事件处理
  99. submitButton.addEventListener('click', function() {
  100.   const content = userInput.value;
  101.   
  102.   // 显示不安全的内容
  103.   displayUnsafe(content);
  104.   
  105.   // 显示安全的内容
  106.   displaySafe(content);
  107.   
  108.   // 使用安全方式创建元素
  109.   const safeElement = createElementSafe('div', { class: 'safe-element' }, content);
  110.   
  111.   // 添加到安全显示区域
  112.   const safeElementContainer = document.createElement('div');
  113.   safeElementContainer.className = 'safe-element-container';
  114.   
  115.   const safeElementTitle = document.createElement('h4');
  116.   safeElementTitle.textContent = '使用安全函数创建的元素:';
  117.   safeElementContainer.appendChild(safeElementTitle);
  118.   
  119.   safeElementContainer.appendChild(safeElement);
  120.   safeDisplay.appendChild(safeElementContainer);
  121. });
  122. // 添加样式
  123. const securityStyle = document.createElement('style');
  124. securityStyle.textContent = `
  125.   .security-container {
  126.     max-width: 800px;
  127.     margin: 20px auto;
  128.     padding: 20px;
  129.     font-family: Arial, sans-serif;
  130.   }
  131.   .input-area {
  132.     margin-bottom: 20px;
  133.   }
  134.   .input-area label {
  135.     display: block;
  136.     margin-bottom: 10px;
  137.     font-weight: bold;
  138.   }
  139.   .input-area textarea {
  140.     width: 100%;
  141.     padding: 10px;
  142.     border: 1px solid #ddd;
  143.     border-radius: 4px;
  144.     box-sizing: border-box;
  145.   }
  146.   .input-area button {
  147.     margin-top: 10px;
  148.     padding: 8px 16px;
  149.     background-color: #4CAF50;
  150.     color: white;
  151.     border: none;
  152.     border-radius: 4px;
  153.     cursor: pointer;
  154.   }
  155.   .input-area button:hover {
  156.     background-color: #45a049;
  157.   }
  158.   .display-area h3 {
  159.     margin-bottom: 10px;
  160.     color: #333;
  161.   }
  162.   .display-box {
  163.     padding: 15px;
  164.     margin-bottom: 20px;
  165.     border: 1px solid #ddd;
  166.     border-radius: 4px;
  167.     min-height: 50px;
  168.     background-color: #f9f9f9;
  169.   }
  170.   #unsafe-display {
  171.     border-color: #f44336;
  172.     background-color: #ffebee;
  173.   }
  174.   #safe-display {
  175.     border-color: #4CAF50;
  176.     background-color: #e8f5e9;
  177.   }
  178.   .safe-element-container {
  179.     margin-top: 15px;
  180.     padding-top: 15px;
  181.     border-top: 1px dashed #ccc;
  182.   }
  183.   .safe-element-container h4 {
  184.     margin-top: 0;
  185.     color: #4CAF50;
  186.   }
  187.   .safe-element {
  188.     padding: 10px;
  189.     background-color: white;
  190.     border: 1px solid #ddd;
  191.     border-radius: 4px;
  192.   }
  193. `;
  194. document.head.appendChild(securityStyle);
复制代码

总结

DOM节点的创建与插入是前端开发中的核心技能,掌握这些技术对于构建动态、交互式的Web应用至关重要。本文从基础概念出发,详细介绍了DOM节点的各种创建方法(如createElement、createTextNode、createDocumentFragment等)和插入方法(如appendChild、insertBefore、insertAdjacentHTML等),并通过丰富的实战案例展示了这些技术的实际应用。

我们还探讨了性能优化策略,包括使用DocumentFragment减少重排、批量操作DOM以及优化重排与重绘。此外,文章还介绍了事件委托、内存泄漏预防和XSS防御等最佳实践,帮助开发者编写更安全、更高效的代码。

在实际开发中,选择合适的DOM操作方法不仅能够提升用户体验,还能显著改善应用性能。随着前端技术的不断发展,现代框架如React、Vue等虽然提供了更高级的抽象,但理解底层DOM操作原理仍然是成为一名优秀前端开发者的必备技能。

通过深入理解和熟练应用本文介绍的技术,开发者将能够构建出更加动态、响应迅速且安全的Web应用,为用户提供卓越的交互体验。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则