• 第12章 DOM操作


    *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; 100px;height: 200px;background-color: #000;"></div>
    <div id="div2" 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和属性

    接口对象 属性名
    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>
    
  • 相关阅读:
    WindowsXP/2000来帮您自动关机
    ASP程序加密/解密方法大揭密
    教你如何在Windows XP使用定时关机命令
    向左滚动 替代 Marquee
    [转]Subversion的权限控制
    Xplanner的安装
    推荐一个不错的浮动广告代码
    微软称20日验证Windows与Office 盗版将黑屏?!
    Internet Explorer 8 Beta 2十大看点
    [转]Subversion的权限控制
  • 原文地址:https://www.cnblogs.com/hycstar/p/14054596.html
Copyright © 2020-2023  润新知