*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. 补:冲刺Day2

    每天举行站立式会议照片: 昨天已完成的工作: 各个成员在 Alpha 阶段认领的任务. 今天各个成员的任务安排. 冲刺Day1博客. 今天计划完成的工作: 成员 任务 高嘉淳 完成登陆.注册 覃泽泰 ...

  2. css3实现立体魔方效果

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  3. 【WHash】更有空间感的感知哈希

    转载请注明出处 背景 在重复图识别领域,对于识别肉眼相同图片,感知哈希效果是很鲁棒的.上一篇文章 [PHash]更懂人眼的感知哈希 介绍的PHash识别效果很好,但是它有一个缺点,只关注低频信息,并没 ...

  4. 使用git客户端免密码进行拉取等相关操作

    前言 如果使用git客户端进行pull或push操作时,遇到有权限的项目总要输入用户名密码,真的是太麻烦了,因此需要稍作修改,然后就可以免密码操作啦! 方法: 进入C盘->用户->你的主机 ...

  5. 20201204-3 opp编程好处

    面向对象编程(Object-Oriented Programming )介绍 对于编程语言的初学者来讲, OOP不是一个很容易理解的编程方式,大家虽然都按老师讲的都知道0OP的三大特性是 继承.封装. ...

  6. Day4 dict和set

    dict  -- dictionary    一组key的集合,包含key与value的对应.        Python内置的字典,在其他语言中称为map,使用key-value存储,具有极快的查找 ...

  7. 交换机配置OSPF负载分担

    组网图形 OSPF负载分担简介 等价负载分担ECMP(Equal-Cost Multiple Path),是指在两个网络节点之间同时存在多条路径时,节点间的流量在多条路径上平均分摊.负载分担的作用是减 ...

  8. Spark性能调优篇二之重构RDD架构及RDD持久化

    如果一个RDD在两个地方用到,就持久化他.不然第二次用到他时,会再次计算. 直接调用cache()或者presist()方法对指定的RDD进行缓存(持久化)操作,同时在方法中指定缓存的策略. 原文:h ...

  9. 最新 obs-studio vs2019 开发环境搭建 代码编译

    距离上一篇文章很久了,重新开始记录 OBS 开发相关情况,第一步就是环境搭建,第二步是构建 OBS-Studio VS 2019 开发环境搭建 下载软件和资源 软件安装没有特别说明的,下载安装即可. ...

  10. 实验题目:python面向对象程序设计

    1.定义并实现一个矩形类Rectangle,其私有实例成员为矩形的左下角与右上角两个点的坐标,能设置左下角和右上角两个点的位置,能根据左下角与右上角两个点的坐标计算矩形的长.宽.周长和面积,另外根据需 ...