zoukankan      html  css  js  c++  java
  • innerHTML误区

    添加元素

    innerHTML属性是个经常用到的原生属性.将html元素用字符串形式设置到父元素中,经常用于将动态拼接的html文本,显示在父容器里.

    拼html字符串的办法已经被认为不妥,正经的办法是使用appendChild()或者createDocumentFragment().不过对于少量的html,当然也能用.

    以前用jQuery库,对应的方法是$('#id').html(),在使用原生属性时,发现与jquery行为有所不同.

    这可能是原生innerHTML属性在执行时,有判断过程.会判断父元素是否可以接受html文本.而jQuery已经处理过这个问题,它的html()方法分析过html字符串.

    以下测试在谷歌浏览器80+版本,x64.

    将div的子元素换成a标记

        <div id = "div1"></div>
    
        let div = document.getElementById('div1');
        div.innerHTML = '<a>link</a>';
    
        // 结果
        <div class="btn" id="div1"><a>link</a></div>
    
    

    这是预期的结果,div里可以放a.如果换成tr就不能成功.

        div.innerHTML = '<tr><td>data</td></tr>';
        
        // 结果 没有加进去
        <div id = "div1"></div>
    
    

    tr是表格table里的子元素,如果div换成table呢

        <table id="tab1"></table>
    
        let tab = document.getElementById('tab1');
        tab.innerHTML = '<tr><td>data</td></tr>';
    
        // 结果
        <table id="tab1"><tbody><tr><td>data</td></tr></tbody></table>
    
    

    成功了.不过不太一样,table里多了一个tbody元素,而innerHTML时并没有这个tbody.这是原生innerHTML属性自己的行为.

    可见,innerHTML也会分析设置的html字符串,如果发现"不合法"的,会拒绝加入.

    使用html对象而不是字符串,可以实现预期效果

        let tr = document.createElement('tr');
        tr.innerHTML = '<td>data</td>';
        tab.appendChild(tr);
    
        // 结果
        <table id="tab1"><tr><td>data</td></tr></table>
    
    

    建立tr元素对象,使用appendChild(tr)加到table,结果里没有tboby元素.

    在项目中还是减少使用innerHTML,使用正规的appendChild()和createDocumentFragment()

    执行js

    当innerHTML一段html里包含js时,是不会执行的.最常见的情况是,从服务器ajax一段包含html,js的页面片段,使用innerHTML设置到容器div,结果js不执行.

    如果使用jQuery的$('#id').html() 方法,不会有这个问题.js执行了.显然,jQuery的html()方法是做了工作的.查看源码时,发现jQuery解析了html字符串.

    innerHTML里的js不执行,可能和浏览器的机制有关.innerHTML里的js可能被当成一般的文本了,所以不执行.

    对于一段html,js混合的文本,尝试过以下方法可以让js执行.

    一. 使用createContextualFragment()方法
        let htmlString='<div>let range = document.createRange();</div><script src="abc.js"></script><script>console.log("hello world")</script>';
        
        // 低版本浏览器不支持这个方法
        let range = document.createRange();
        let fragment = range.createContextualFragment(htmlString);
    
        document.body.appendChild(fragment);
    
    

    这个方法解析htmlString,然后返回DocumentFragment对象,加到文档后,js会执行.

    但是有一个问题,执行时没有按script标记的顺序.先执行了第二个script,后执行的abc.js

    二. 解析法

    使用js生成新的script标记,再添加到文档,是可以执行的.这个办法是分析htmlString,将script找出来,再重新生成一次.

    为了让js顺序执行,可以在解析时将外联的js下载,变成内联的.具体做法,递归解析htmlString.

    这段代码解析html字符串,返回一个文档片段对象,里面的js标记是重新生成的,对于外联会下载成内联,顺序执行.

    代码有局限.html字符串中的script标记只能是第一子节点,不能包含在其它dom元素内,因为递归方法只遍历了子节点,没有遍历后代节点.

    只能满足相对简单的script标记顺序执行,对于有复杂依赖的js,也不能保证顺序执行.

        // 解析html, val:html字符串 ,onReady:解析完成后的文档片段对象
        function parseHtml = (val, onReady) => {
          let framgSource;
          if (typeof val === 'string') {
              let range = document.createRange();
              framgSource = range.createContextualFragment(val);
          } else if (val instanceof DocumentFragment) {
              framgSource = val;
          } else if (val.length) {
              framgSource = document.createDocumentFragment();
              framgSource.append(...val);
          } else {
              framgSource = document.createDocumentFragment();
              framgSource.append(val);
          }
          // 放入fragment.(解析放入)
          let fragment = document.createDocumentFragment();
          _parseHtmlNodeLoad(fragment, framgSource, onReady);
        };
    
        // 递归
        function _parseHtmlNodeLoad = (toFragm, fromFragm, onReady) => {
            if (fromFragm.firstChild === null) {
                onReady(toFragm);
                return;
            }
            // script元素.设置到innerhtml时不会执行,要新建一个script对象,再添加
            if (fromFragm.firstChild.nodeName === 'SCRIPT') {
                let newScript = document.createElement('script');
                let src = fromFragm.firstChild.src;
                if (src) {
                    // 外联的script,要加载下来,否则有执行顺序问题.外联的没有加载完,内联的就执行了.如果内联js依赖外联则出错.
                    // 这个办法是获取js脚本,是设置到生成的script标签中.(变成内联的了)
                    fetch(src).then(res => res.text())
                        .then((js) => {
                            newScript.innerHTML = js;
                            toFragm.append(newScript);
                            fromFragm.removeChild(fromFragm.firstChild);
                            _parseHtmlNodeLoad(toFragm, fromFragm, onReady);
                        });
                } else {
                    // 内联的直接设置innerHtml
                    newScript.innerHTML = fromFragm.firstChild.innerHTML;
                    toFragm.append(newScript);
                    fromFragm.removeChild(fromFragm.firstChild);
                    _parseHtmlNodeLoad(toFragm, fromFragm, onReady);
                }
            } else {
                // 其它元素
                toFragm.append(fromFragm.firstChild);
                _parseHtmlNodeLoad(toFragm, fromFragm, onReady);
            }
        };
    
    

    缓存文档片段

    有时需要将文档中的一部分dom缓存起来,需要时再加入文档中.

    由于innerHTML是字符串,所以一些dom的属性不会保存.比如select元素,在缓存前选择了"two",使用innerHTML缓存在还原时,选择会变成默认的"one".

    使用node.cloneNode(true)复制节点方法也不行,也保存不了select元素的选中状态.

    可以使用DocumentFragment对象的append()方法,添加这个div后,div会脱离文档,缓存到文档片段对象中.在放入文档中,它的状态不变.

    这个办法没有"加工"要缓存的元素,只是将它移动了位置.从文档对象移动到文档片段对象.

        // select 选择了two
        <select>
          <option value="1">one</option>
          <option value="2">two</option>
          <option value="3">three</option>
        </select>
    
        // 使用innerHTML,将div的所有子元素存到变量中
        let dom = div.innerHTML;
        div.innerHTML = '';
    
        // 还原 (选择状态会丢失)
        div.innerHTML = dom;
    
        // cloneNode(true) (复制select节点,选择状态也会丢失)
        dom = div.cloneNode(true);
        div.append(dom);
    
        // 使用DocumentFragment对象的append()添加到文档片段对象,再放入文档中,状态不变.
        dom = document.createDocumentFragment();
        dom.append(div);
        div.append(dom);
    
    
  • 相关阅读:
    网络协议 19
    网络协议 18
    网络协议 17
    网络协议 16
    网络协议 15
    .NET基础知识(01)-值类型与引用类型
    .NET基础知识(02)-拆箱与装箱
    网络协议 14
    网络协议 13
    网络协议 12
  • 原文地址:https://www.cnblogs.com/mirrortom/p/12497239.html
Copyright © 2011-2022 走看看