第12章 DOM操作

摘要:
有两种方式可以解决通过innnerHTML将该字符串直接注入到它的特定父元素中,该父元素提前使用内置的document.creatElemnet创建好HTML字符串可以在使用对应父元素包装后,直接注入到任意容器元素中需要包装在其他元素中的元素元素名称父级元素,......,,,,............,......使用具有multiple属性的元素,因为它不会自动检查任何包含在其中的选项对的兼容处理需要一个额外的,否则不能正确生成//将元素标签转换为一系列DOM节点functiongetNodes{//需要特殊父级容器的元素映射表。
目录

*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   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     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   style="display: none; 100px;height: 200px;background-color: #000;"></div>
<div   style=" 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 = {   // 保存结果
                 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和属性

接口对象属性名
ElementclientHeight, clientLeft, clientTop, clientWidth,
focus, getBoundingClientRect, getClientRects, innerText,
offsetHeight. offsetLeft, offsetParent, offsetTop, offsetWidth,
outerText, scrollByLines, scrollByPages, scrollHeight,
scrollIntoView, scroollIntoViewIfNeeded,
scrollLeft, scrollTop, scrollWidth
MouseEventlayerX, layerY, offsetX, offsetY
WindowgetComputedStyle, 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操作》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇《马哥出品高薪linux运维教程》wingkeung学习笔记-linux基础入门课程NGUI的输入框制作(attach- input filed script的使用)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android JNI开发高级篇有关Android JNI开发中比较强大和有用的功能就是从JNI层创建、构造Java的类或执行Java层的方法获取属性等操作。 一、类的相关操作 1. jclass FindClass(JNIEnv *env, const char *name);

有关Android JNI开发中比较强大和有用的功能就是从JNI层创建、构造Java的类或执行Java层的方法获取属性等操作。 一、类的相关操作 1.jclass FindClass(JNIEnv *env, const char *name);查找类 该函数可能做过Java开发的不会陌生,这个是JNI层的实现,需要注意的是第二个参数为const char...

【转】Python 数据库连接池

    python编程中可以使用pymysql进行数据库连接及增删改查操作,但每次连接mysql请求时,都是独立的去请求访问,比较浪费资源,而且访问数量达到一定数量时,对mysql的性能会产生较大的影响。因此实际使用中,通常会使用数据库的连接池技术,来访问数据库达到资源复用。 python的数据库连接池包:DBUtils DBUtils提供两种外部接...

select 无限级联动。省市县三级联动。jquery插件

   /*省市县 三级联动, 后台提取json数据<script src="http://t.zoukankan.com/res/PCASelect.js" type="text/javascript"></script><script type="text/javascript">    $(document).r...

avue elementui 常用操作

1.获取表格checkbox的行的数据,绑定事件selection-change,回调返回值为:devicesGridData,和下面代码对应 <el-table :data="devicesGridData" ref="multipleTable" @selection-change="handleSelectionChange" :row-...

写一个简易的java项目(五) websocket 弹幕 -1

目的:websocket做弹幕  用到的技术:springboot +websocket +uniapp (只写了后台) 菜鸟:https://www.runoob.com/html/html5-websocket.html 这是最后结果的展示: WebSocket是什么?为什么用它? 全双工通信的协议。允许服务端主动向客户端推送数据。 后台代码: 第一...

Openfire验证机制的修改(整合自定义用户表)

注意: 按照openfire官方的只是修改openfire.xml是错误的 可以不修改openfire.xml文档 步骤: 正常步骤安装完openfire 停止openfire服务 直接在数据库运行: UPDATE `openfire`.`ofProperty` SET propValue='org.jivesoftware.openfire.auth....