*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. AcWing 180. 排书

    AStar 最坏情况\(O(log_2560 ^ 4)\) 用\(AStar\)算法做了这题,程序跑了\(408ms\). 相比于\(IDA*\)的\(100ms\)左右要慢上不少. 且\(A*\)由 ...

  2. nginx学习之——CentOS6.0下安装nginx

    1.下载对应nginx版本 #注:下载地址:http://nginx.org/download/ wget -c http://nginx.org/download/nginx-1.10.3.tar. ...

  3. js实现刮刮卡抽奖

    刮刮卡抽奖是前端活动页常见的功能: 链接:图像擦除插件(下载及教程讲解)    推荐理由:无缝刮痕,兼容性好,上手简单   插件有些要修改的地方,打开图像擦除插件后可以看下方网友讨论,或者直接下载本博 ...

  4. CIBN手机电视8.3.2永久VIP

    一款互联网电视的手机客户端.可以观看最新的电影和电视剧,还会为你推荐人气热门电影,让你不会错过每一部精彩的大片,以去除app内的所有可见广告,解锁VIP特权,无需登录直接使用! 下载地址:https: ...

  5. 抖音视频背景音乐提取工具v1.0

    使用方法:id就是你点那个音乐分享,复制链接,然后链接有个ID(userid=后面数字就是id),就是那个,输入ID之后得到链接,浏览器新建下载,复制你得到的链接就行了(结果空白多解析几次就行了)

  6. django 取出数据库的时间与当前时间相加减

    1 转换时区utc比北京时间慢八个小时 from datetime import tzinfo, timedelta, datetime ZERO = timedelta(0) class UTC(t ...

  7. mysql批量刷新用户密码

    不知道用户密码,并且不改变用户密码的情况下,批量刷新MySQL数据库用户的密码 select concat('alter user \'',user,'\'@\'',host,'\' identifi ...

  8. 安卓 Android Studio 下载

    http://www.android-studio.org/    下载地址 https://blog.csdn.net/qq_41976613/article/details/91432304    ...

  9. Python高级语法-对象实例对象属性-类与实例,class方法静态方法等(4.6.1)

    @ 目录 1.说明 2.代码 关于作者 1.说明 python中属性:类属性,实例属性 方法:类方法,实例方法,静态方法 想修改类属性,只能是类方法,因为只有类方法把cls(类)传入数据里面 静态方法 ...

  10. javascript之原型、原型链

    一.原型: 1. 任何函数都有prototype属性(对象才有属性,函数也是对象): 2. 函数的prototype属性的值是个对象,这个对象就是原型(对象): 3. 作用:通过构造函数创建出来的对象 ...