*1. 向DOM中注入HTML

1.1 将HTNL字符串转换成DOM

  • 转换的步骤如下所示:

    • 确保HTML字符串是合法有效的
    • 将它包裹在任意符合浏览器规则要求的闭合标签中
    • 使用innerHTML将这串HTML插入到虚拟的DOM元素中
    • 提取该DOM节点

预处理HTML源字符串

// 确保自闭合元素被正确解释

// 单标签
const tags = /^(area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i; // 通过正则把错误的单标签转换为标签对
function convert(html) {
return html.replace(/(<(\w+)[^>]*?)\/>/g, (all, front, tag) => {
return tags.test(tag) ? all : front + "></" + tag + ">";
});
} console.log(convert("<a/>"));
// <a></a>
console.log(convert("<hr />"));
// <hr />

包装HTML

  • 根据HTML语义,一些HTML元素必须包装在某些容器元素中。有两种方式可以解决(都需要构建问题元素和容器之间的映射关系)

    • 通过innnerHTML将该字符串直接注入到它的特定父元素中,该父元素提前使用内置的document.creatElemnet创建好
    • HTML字符串可以在使用对应父元素包装后,直接注入到任意容器元素中
  • 需要包装在其他元素中的元素
元素名称 父级元素
<option>, <optgroup> <select multiple>...</select>
<legend> <fieldset>...</fieldset>
<thead>, <tbody>, <tfoot>,
<colgroup>, <caption>
<table>...</table>
<tr> <table><thead>...</thead></table>
<table><tbody>...</tbody></table>
<table><tfoot>...</tfoot></table>
<td>, <th> <table><tbody><tr>...</tr></tbody></table>
<col> <table>
    <tbody></tbody>
    <colgroup>...</colgroup>
</table>

使用具有multiple属性的<select>元素,因为它不会自动检查任何包含在其中的选项
对<col>的兼容处理需要一个额外的,否则<colgroup>不能正确生成

// 将元素标签转换为一系列DOM节点
function getNodes(htmlString, doc) {
// 需要特殊父级容器的元素映射表。
// 每个条目包含新节点的深度,以及父元素的HTML头尾片段
const map = {
"<td": [3, "<table><tbody><tr>", "</tr></tbody></table>"],
"<th": [3, "<table><tbody><tr>", "</tr></tbody></table>"],
"<tr": [2, "<table><thead>", "</thead></table>"],
"<option": [1, "<select multiple>", "</select>"],
"<optgroup": [1, "<select multiple>", "</select>"],
"<thead": [1, "<table>", "</table>"],
"<tbody": [1, "<table>", "</table>"],
"<tfoot": [1, "<table>", "</table>"],
"<colgroup": [1, "<table>", "</table>"],
"<caption": [1, "<table>", "</table>"],
"<col": [2, "<table><tbody></tbody><colgroup>", "</colgroup></table>"]
}
const tagName = htmlString.match(/<\w+/);
let mapEntry = tagName ? map[tagName[0]] : null;
// 如果映射表中有匹配,使用匹配结果
// 如果没有,则构造空的父标记,深度为0作为结果
if (!mapEntry) { mapEntry = [0, "", ""] }
// 创建用于包含新节点的元素,如果传入了文档对象,则使用传入的,否则使用当前的
let div = (doc || document).createElement("div");
// 使用匹配得到的父级容器元素,包装后注入新创建的元素中
div.innerHTML = mapEntry[1] + htmlString + mapEntry[2];
// 参照映射关系定义的深度,向下遍历刚刚创建的DOM树,最终得到新创建的元素
while (mapEntry[0]--) {
div = div.lastChild;
}
// 返回新创建的元素
return div.childNodes;
}

1.2 将DOM元素插入到文档中

// 新增frgment参数,新增节点将被添加到这个DOM片段中
function getNodes(htmlString, doc, fragment) {
const map = {
"<td": [3, "<table><tbody><tr>", "</tr></tbody></table>"],
"<th": [3, "<table><tbody><tr>", "</tr></tbody></table>"],
"<tr": [2, "<table><thead>", "</thead></table>"],
"<option": [1, "<select multiple>", "</select>"],
"<optgroup": [1, "<select multiple>", "</select>"],
"<thead": [1, "<table>", "</table>"],
"<tbody": [1, "<table>", "</table>"],
"<tfoot": [1, "<table>", "</table>"],
"<colgroup": [1, "<table>", "</table>"],
"<caption": [1, "<table>", "</table>"],
"<col": [2, "<table><tbody></tbody><colgroup>", "</colgroup></table>"]
}
const tagName = htmlString.match(/<\w+/);
let mapEntry = tagName ? map[tagName[0]] : null;
if (!mapEntry) { mapEntry = [0, "", ""] }
let div = (doc || document).createElement("div");
div.innerHTML = mapEntry[1] + htmlString + mapEntry[2];
while (mapEntry[0]--) {
div = div.lastChild;
}
// 添加节点到DOM片段中
if (fragment) {
while (div.firstChild) {
fragment.appendChild(div.firstChild);
}
}
return div.childNodes;
}
<div id="test"><b>Hello</b>, I'm Wango!</div>
<div id="test2"></div>
<script>
document.addEventListener("DOMContentLoaded", () => { // 在DOM的国歌位置插入DOM片段
function insert(elems, args, callback) {
if (elems.length) {
const doc = elems[0].ownerDocument || elems[0];
const fragment = doc.createDocumentFragment();
const scripts = getNodes(args, doc, fragment);
const first = fragment.firstChild; if (first) {
for (let i =0; elems[i]; i++) {
callback.call(elems[i], i > 0 ? fragment.cloneNode(true) : fragment);
}
}
}
} const divs = document.querySelectorAll("div");
insert(divs, "<b>Name:</b>", function(fragment) {
this.appendChild(fragment);
}); insert(divs, "<span>First</span><span>Last</span>", function (fragment) {
this.parentNode.insertBefore(fragment, this);
});
});
</script>

2. DOM的特性和属性

通过DOM方法和属性访问特性值

<div></div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const div = document.querySelector("div"); // HTML DOM的原生特性,通常能被属性表示
div.setAttribute("id", "news-01");
console.log(div.id);
// news-01
console.log(div.getAttribute("id"));
// news-01
div.id = "news-02";
console.log(div.getAttribute("id"));
// news-02 // 但自定义特性不能被元素属性表示,需要使用
// setAttribute()和getAttribute()
div.setAttribute("data-news", "breaking");
console.log(div.getAttribute("data-news"));
// breaking
});
</script>

在HTML5中,为遵循规范,建议使用data-作为自定义属性的前缀,方便区分自定义特性和原生特性

3. 令人头疼的样式特性

常用的style元素属性是一个对象,该对象的属性与元素标签内指定的样式相对应。

3.1 样式在何处

<style>
div {
font-size: 1.8em;
border: 0 solid gold;
}
</style>
<div style="color: #000;" title="Hello"></div>
<script>
document.addEventListener("DOMContentLoaded", () => {
const div = document.querySelector("div");
// 内联样式被记录
console.log(div.style.color);
// rgb(0, 0, 0) // <style>标签内定义的的样式没有被记录
console.log(div.style.fontSize === "1.8em");
// false
console.log(div.style.borderWidth === "0");
// false
// 样式对象中不反应从CSS样式表中继承的任何样式信息 // 新赋值的样式被记录
div.style.borderWidth = "10px";
console.log(div.style.borderWidth);
// 10px
});
</script>

内联样式中的任何值,都优先于样式表继承的值(即使样式表规则使用!important的注释)

3.2 样式属性命名

一种访问样式的简单方法

<div style="color: red;font-size: 10px;background-color: #eee;"></div>
<script>
// 处理样式函数
// 如果传入value,将相应样式属性值赋值为value
// 如果没有传入value,则返回改样式属性值
// 可以通过它来设置/读取样式属性
function style(elem, key, value) {
// 将属性名转为驼峰格式
// 以同时兼容驼峰式和连字符式样式名
key = key.replace(/-([a-z])/ig, (all, letter) => {
return letter.toUpperCase();
});
// 如果传入value则将相应样式属性值设置为value
if (typeof value !== "undefined") {
elem.style[key] = value;
} return elem.style[key];
} document.addEventListener("DOMContentLoaded", () => {
const div = document.querySelector("div");
// 设置属性
style(div, "font-size", "20px");
style(div, "background-color", "#000"); console.log(div.style.fontSize === "20px");
// true
console.log(div.style.backgroundColor === "rgb(0, 0, 0)");
// true // 读取属性
console.log(style(div, "font-size"));
// 20px
console.log(style(div, "background-color"));
// rgb(0, 0, 0)
});
</script>

3.3 获取计算后样式

一个元素的计算后样式(computed style)都是应用在该元素上的所有样式的组合,这些样式包括样式表、元素的style内联样式、、浏览器内置样式、JS脚本对style所作的各种操作等

<style>
div {
background-color: #ffc;
display: inline;
font-size: 1.8em;
border: 1px solid crimson;
color: green;
}
</style> <div style="color: crimson;" id="test" title="hello"></div> <script>
// 用于获取元素计算后属性
function fetchComputedStyle(elem, property) {
// getComputedStyle是浏览器提供的全局函数,可直接调用
const computedStyle = getComputedStyle(elem);
if (computedStyle) {
// 将传入的样式名转换为中横线分割
// 以同时兼容驼峰式和连字符式样式名
property = property.replace(/([A-Z])/g, "-$1".toLowerCase());
// getComputedStyle返回的对象提供了getPropertyValue方法
// 这个方法接收中横线分割格式的样式名
return computedStyle.getPropertyValue(property);
}
} document.addEventListener("DOMContentLoaded", () => {
const div = document.querySelector("div");
console.log(fetchComputedStyle(div, "background-color"));
// rgb(255, 255, 204)
console.log(fetchComputedStyle(div, "color"));
// rgb(220, 20, 60) 返回的是内联样式中color的值,内联样式将css样式覆盖了
console.log(fetchComputedStyle(div, "borderWidth"));
// 1px
console.log(fetchComputedStyle(div, "borderTop"));
// 1px solid rgb(220, 20, 60)
});
</script>

3.4 测量元素的高度和宽度

  • height和width的默认值都是auto,所以无法获取准确的值
  • 使用offsetHeight和offsetWidth,但这两个值包含了padding值
  • 隐藏元素(display: none)没有尺寸,offsetHeight和offsetWidth为0
  • 获取隐藏元素在非隐藏状态下的尺寸可以先取消隐藏,获取值,再隐藏,具体为:
    • 将display设置为block(可以获取值了,但元素会可见)
    • 将visibility设置为hidden(使元素不可见,但元素位置会显示一个空白)
    • 将position设置为absolute(将元素移出正常的可视区)
    • 获取元素尺寸
    • 恢复先前更改的属性
<div id="div1" style="display: none;width: 100px;height: 200px;background-color: #000;"></div>
<div id="div2" style="width: 300px;height: 400px;background-color: #00ff00;"></div>
<script>
(function(scope) { // 使用立即执行函数创建私有作用域
const PROPERTIES = {
position: "absolute",
visibility: "hidden",
display: "block"
}
scope.getDimensions = elem => {
const previous = {}; // 用于保存原有属性值
for (let key in PROPERTIES) {
previous[key] = elem.style[key]; // 保存原有值
elem.style[key] = PROPERTIES[key]; // 替换设置
}
const results = { // 保存结果
width: elem.offsetWidth,
height: elem.offsetHeight
}
for (let key in PROPERTIES) { // 还原设置
elem.style[key] = previous[key];
} return results;
}
})(window); document.addEventListener("DOMContentLoaded", () => {
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2"); console.log(getDimensions(div1).height);
// 200
console.log(getDimensions(div1).width);
// 100
console.log(getDimensions(div2).height);
// 400
console.log(getDimensions(div2).width);
// 300
});
</script>

检查offsetHeight和offsetWidth属性值是否为0,可以非常有效地确定一个元素的可见性

4. 避免布局抖动

  • 抖动原因:代码对DOM执行一系列(通常是不必要的)连续的读取和写入时(浏览器执行大量重新计算),浏览器无法优化布局操作

引起布局抖动的API和属性

接口对象 属性名
Element clientHeight, clientLeft, clientTop, clientWidth,
focus, getBoundingClientRect, getClientRects, innerText,
offsetHeight. offsetLeft, offsetParent, offsetTop, offsetWidth,
outerText, scrollByLines, scrollByPages, scrollHeight,
scrollIntoView, scroollIntoViewIfNeeded,
scrollLeft, scrollTop, scrollWidth
MouseEvent layerX, layerY, offsetX, offsetY
Window getComputedStyle, scrollBy, scrollTo, scroll, scrollY
Frame,
Document, Image
height, width
<div id="div1">Hello</div>
<div id="div2">World</div>
<div id="div3">!!!!!!!!</div> <script>
// 获取元素
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2");
const div3 = document.getElementById("div3"); // 执行一系列来纳许的读写操作,修改DOM使得布局失效
const div1Width = div1.clientWidth;
div1.style.width = div1Width/2 + "px"; const div2Width = div2.clientWidth;
div2.style.width = div2Width/2 + "px"; const div3Width = div3.clientWidth;
div3.style.width = div3Width/2 + "px"; // 防抖的一种方法:批量读写 // 批量读取所有布局属性
const div1Width = div1.clientWidth;
const div2Width = div2.clientWidth;
const div3Width = div3.clientWidth; // 批量写入所有布局属性
div1.style.width = div1Width/2 + "px";
div2.style.width = div2Width/2 + "px";
div3.style.width = div3Width/2 + "px";
</script>

第12章 DOM操作的更多相关文章

  1. jQuery 第二章 实例方法 DOM操作选择元素相关方法

    进一步选择元素相关方法:  .get() .eq() .find() .filter() .not() .is() .has() .add()集中操作  .end()回退操作 .get() $(&qu ...

  2. 第3章 jQuery的DOM操作

    一.  DOM 分为DOM核心,HTML-DOM和CSS-DOM 1.DOM核心 不专属与javascript. 获取对象:document.getElementsByTagName('div') 获 ...

  3. 第三章(jQuery中的DOM操作)

    3.1 DOM 操作分类 ①DOM Core 包括(getElementById() , getElementsByTagName() , getAttribute() , setAttribute( ...

  4. 第四章 JavaScript操作DOM对象

    第四章   JavaScript操作DOM对象 一.DOM操作 DOM是Document Object Model的缩写,即文档对象模型,是基于文档编程的一套API接口,1988年,W3C发布了第一级 ...

  5. jQuery系列 第七章 jQuery框架DOM操作

    第七章 jQuery框架的选择器 jQuery框架继承和优化了JavaScript访问DOM对象的特性,我们使用jQuery框架提供的api可以更加方便的操作DOM对象. 7.1 创建DOM节点 使用 ...

  6. JQuery制作网页—— 第三章 JavaScript操作DOM对象

    1. DOM:Document Object Model(文档对象模型):          DOM操作:                   ●DOM是Document Object Model的缩 ...

  7. 第二章 jquery的dom操作

    三个方面    dom核心,html-dom和css-dom 一. 1.dom core核心 document.getElementsByTagName("form")  获取表单 ...

  8. JavaScript交互式网页设计 • 【第7章 jQuery操作 DOM】

    全部章节   >>>> 本章目录 7.1 DOM 对象和 jQuery 对象 7.1.1 DOM 对象 7.1.2 jQuery 对象 7.1.3 jQuery 对象和 DOM ...

  9. javascript dom编程艺术笔记第三章:DOM操作的5个基本方法

    JavaScript的 DOM操作,主要是对DOM这三个字母中D.O.M的操作.D代表的是document(文档),即我们可以使用javascript对文档进行操作,O代表的是object(对象),对 ...

随机推荐

  1. Linq to SQL 语法整理(子查询 & in操作 & join )

    子查询 描述:查询订单数超过5的顾客信息 查询句法: var 子查询 = from c in ctx.Customers where (from o in ctx.Orders group o by ...

  2. 效率神器-uTools推荐和使用

    提高办公开发效率...非常好用  功能很多很全,官网:https://u.tools/ 文档:https://u.tools/docs/guide/about-uTools.html

  3. 传输层-Transport Layer(上):传输层的功能、三次握手与四次握手、最大-最小公平、AIMD加法递增乘法递减

    第六章 传输层-Transport Layer(上) 6.1传输层概述 在之前的几章内容中,我们自底向上的描述了计算机网络的各个层次,还描述了一些处于不同层次下的经典网络协议(如以太网.无线局域网.或 ...

  4. 基数排序(Radix Sort)

    基数排序(Radix Sort) 第一趟:个位 收集: 第二趟:十位 第三趟:百位 3元组 基数排序--不是基于"比较"的排序算法 递增就是把收集的过程返过来 算法效率分析 需要r ...

  5. 八、TestNG忽略测试

    一个TestNG  测试类中如果有的方法不想测试可以使用 enabled 属性 enabled = false  该方法不参与测试 enabled = true  该方法参与测试 @Test 不写en ...

  6. screw一键生成数据库文档

    1. 简介   在项目开发和交付阶段,数据库文档是必不可少的.对于大型项目多个数据库几百甚至几千张表来说,手写数据库文档必然是耗时且痛苦的.因此需要一个插件自动生成文档.   screw提供了多种文件 ...

  7. Python实现全自动购买火车票!抢票回家过年咯

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理这个是实现结果,因为一天只能取消三次,所以最后一步点击确认被我注释了1.首先实现使用selenium登 ...

  8. JavaScript实现自定义右键菜单

    JavaScript实现自定义右键菜单,思路如下: 1. 屏蔽默认右键事件: 2. 隐藏自定义的菜单模块(如div.ul等): 3. 右键点击特定或非特定区域,显示菜单模块: 4. 再次点击,隐藏菜单 ...

  9. 图解JanusGraph系列 - JanusGraph指标监控报警(Monitoring JanusGraph)

    大家好,我是洋仔,JanusGraph图解系列文章,实时更新~ 图数据库文章总目录: 整理所有图相关文章,请移步(超链):图数据库系列-文章总目录 源码分析相关可查看github(码文不易,求个sta ...

  10. MySQL管理基础

    #1.数据库连接管理 mysql命令说明 第一个功能:连接数据库(在前面mysql命令的使用里面讲解了,这里就不讲解了) 第二个功能:mysql客户端自带的命令功能 mysql命令的使用(mysql ...