「读书笔记」第四版JavaScript高级程序设计(第十四章)

前言

单纯的笔记,没别的

节点的类型

节点类型

一共有12种节点的类型,节点类型通过nodeType获取,使用数字1~12表示

  • nodeType,节点的类型
  • nodeName,取决于节点的类型,对于Element类型的元素,nodeName等于标签名
  • nodeValue,取决于节点的类型,对于Element类型的元素,nodeValue等于null

节点关系

  • childNodes属性,包含NodeList类数组对象。NodeList可以直接使用中括号NodeList[0], NodeList.item(1), 访问其中的元素。Array.from转成数组对象
  • parentNode属性,指向父节点
  • previousSibling,前一个兄弟节点,第一个的前一个是null,
  • nextSibling,后一个兄弟节点, 最后一个的后一个是null,
  • firstChild,第一个子节点
  • lastChild,最后一个子节点
  • hasChildNodes(), 如果存在子节点返回true

操纵节点

  • parentNode.appendChild(), 在childNodes列表末尾添加元素,并返回新的元素。如果是已有元素,则会移动现在的节点。
  • parentNode.iertBefore(插入节点,参考节点),如果参考节点是null,会插入到最后一个节点。如果参考节点是firstChild,则参入到第一个节点。
  • parentNode.replaceChild(替换节点,参考节点)
  • parentNode.removeChild(删除的节点)
  • cloneNode(boolean), boolean传false只会复制当前的节点,传true会复制当前节点极其子节点,cloneNode不会复制由js添加的属性,比如事件处理程序。
  • normalize,规范化文本节点

Document类型

文档对象 document 是 HTMLDocument 的实例(HTMLDocument 继承 Document),表示整个 HTML 页面。document 是 window 对象的属性,因此是一个全局对象。Document的nodeType类型等于9

文档子节点

  • document.documentElement,返回html元素
  • document.childNodes[0], 返回html元素
  • body属性,返回body元素
  • doctype属性, 返回
  • title,包含标签的文本</li> <li>URL, 当前页面完整的url</li> <li>domain,当前页面的域名, 可以修改,但是只能修改一次</li> <li>referrer,页面的来源</li> </ul> <p> document.domain, 是可以修改的,但是修改有限制,比如www.baidu.com,可以修改为image.baidu.com。</p> <p> 当一个页面嵌套一个iframe时,可以修改iframe的domain属性,两个文档的domain属性一样时,可以共享javascript对象。</p> <h3>定位元素</h3> <ul> <li>getElementById</li> <li><p>getElementsByTagName,返回HTMLCollection类数组对象</p> <ul> <li>HTMLCollection对象的namedItem方法,可以根据指定节点name,获取元素</li> <li>document.getElementsByTagName('*'), 会返回所有元素</li> </ul></li> <li>getElementsByName,获取指定name的元素</li> <li>document.images,获取所有的图片元素</li> <li>document.links,获取所有包含href属性的,a标签</li> <li>document.forms,获取所有的form元素</li> </ul> <pre><code class="js"> const images = document.getElementsByTagName('img') // 获取name属性为t1的图片 images.namedItem('t1')</code></pre> <h3>文档写入</h3> <ul> <li>write,写入文本</li> <li>writeln,写入文本并换行</li> <li>open, 打开网页</li> <li>close, 关闭网页</li> </ul> <h4>write</h4> <ol> <li>write如果发生在oload之后执行,write方法会重写整个页面。</li> <li>write如果发生在oload之后前执行, write方法会写在当前script之后的位置。write在写入<code>script</code>标签的时候,</li> </ol> <h2>Element类型</h2> <p>Element类型的nodeType等于1,nodeName等于标签名,nodeValue等于null.</p> <p>nodeName和tagName返回同样的值,但是tagName通常返回标签名的大写</p> <h3>HTML</h3> <p>所有HTML元素都是HTMLElement类型,HTMLElement类型继承自Element类型,并增加了一些自己的属性。</p> <p>常用的属性:</p> <ul> <li>id</li> <li>title,额外信息</li> <li>dir, 书写方向</li> <li>className</li> </ul> <h4>属性方法</h4> <ul> <li>getAttribute,获取属性,属性名不区分大小写,ID等同于id。</li> <li>setAttribute(key, value),设置属性, 属性名会自动规范为小写</li> <li>removeAttribute,删除属性,不仅仅是删除value。会删除key和value</li> </ul> <h4>attributes属性</h4> <p>attributes属性包含一个NamedNodeMap实例,元素的每一个属性都保存在NamedNodeMap实例中。</p> <ul> <li>getNamedItem(name),返回NamedNodeMap实例中,属性等于name的节点</li> <li>removeNamedItem(name),删除属性等于name的节点</li> <li>setNamedItem(node),设置属性节点</li> </ul> <pre><code class="js">// id1和id2是等同的 const id1 = element.attributes.getNamedItem("id").nodeValue; const id2 = element.attributes["id"].nodeValue; // 删除属性 element.attributes.removeNamedItem("id") // 设置属性 const node = document.createAttribute("class"); node.value = "democlass"; element.attributes.setNamedItem(node); // 或者可以直接修改属性值 element.attributes["id"].nodeValue = "someOtherId";</code></pre> <h4>创建元素</h4> <ul> <li>document.createElement(),创建元素</li> </ul> <h2>Text类型</h2> <p>nodeType等于3,nodeValue等于文本内容,nodeName值等于#text,不支持子节点。parentNode的是Element对象。取得文本节点的引用后,可以直接通过nodeValue修改</p> <pre><code class="js"> const textNode = document.getElementsByClassName('color_h1')[0] textNode.firstChild.nodeValue = 'Hello' // 或者修改data属性,效果和nodeValue一样 textNode.firstChild.data = 'Hello'</code></pre> <h3>创建文本节点</h3> <ul> <li>document.createTextNode()</li> </ul> <pre><code class="js">let element = document.createElement("div"); element.className = "message"; // 创建文本节点,并插入到element中 let textNode1 = document.createTextNode("Hello"); element.appendChild(textNode1); // 可以插入多个文本节点 let textNode2 = document.createTextNode("World!"); element.appendChild(textNode2); document.body.appendChild(element);</code></pre> <h3>规范化文本节点</h3> <p>在包含多个文本节点的元素上调用normalize方法,会将文本节点合并为一个文本节点</p> <pre><code class="js">let element = document.createElement("div"); element.className = "message"; // 创建文本节点,并插入到element中 let textNode1 = document.createTextNode("Hello"); element.appendChild(textNode1); // 可以插入多个文本节点 let textNode2 = document.createTextNode("World!"); element.appendChild(textNode2); // 将会合并文本节点 element.normalize()</code></pre> <h2>Comment类型</h2> <p>注释节点类型,nodeType等于8,nodeValue是注释的内容,nodeName值等于#comment",parentNode是Document或者Element,不支持子节点</p> <pre><code class="js">// <div id="test"><!-- 注释 --></div> let element = document.getElementById("test"); // 注释节点 let comment = div.firstChild; // 注释节点的内容 console.log(comment.data) console.log(comment.nodeValue)</code></pre> <h3>创建注释节点</h3> <ul> <li>document.createComment()</li> </ul> <h2>CDATASection类型</h2> <p>nodeType等于4, 其他内容略过……</p> <h2>DocumentType类型</h2> <p>nodeType等于10,不支持动态创建,parentNode等于Docuemtn对象,可以使用document.doctype获取DocumentType对象。</p> <ul> <li>name,文档类型的名称, <code><!DOCTYPE</code> 后面的内容</li> </ul> <pre><code class="html"><!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document

    DocumentFragment类型

    文档片段, nodeType等于11,DocumentFragment可以理解为一个仓库,DocumentFragment里存储着所有要添加到文档里的节点

    创建文档片段

    • document.createDocumentFragment

    使用

    // 如果不使用文档片段,浏览器会渲染3次
    let ul = document.getElementById("myList");
    for (let i = 0; i < 3; ++i) {
        let li = document.createElement("li");
        li.appendChild(document.createTextNode(`Item ${i + 1}`));
        ul.appendChild(fragment);
    }
    
    // 使用文档片段后,浏览器只会渲染一次
    let fragment = document.createDocumentFragment();
    let ul = document.getElementById("myList");
    for (let i = 0; i < 3; ++i) {
        let li = document.createElement("li");
        li.appendChild(document.createTextNode(`Item ${i + 1}`));
        fragment.appendChild(li);
    }
    ul.appendChild(fragment);

    Attr类型

    Attr节点存在在元素的attributes属性上,nodeType等于2,nodeName等于属性名,nodeValue等于属性值,Attr类型虽然是节点,但不是DOM树的一部分

    // attr节点的name为align
    let attr = document.createAttribute("align");
    // attr节点的value为left
    attr.value = "left";
    // 为元素设置属性
    element.setAttributeNode(attr);

    DOM编程

    动态脚本

    对于动态插入的内联script脚本,如果是使用innerHTML插入的script,不会执行。只能通过appendChild,等DOM的API插入节点。

    const script = document.createElement("script"); 
    script.type = "text/javascript";
    
    script.appendChild(document.createTextNode(code));
    
    
    // 可以执行
    document.body.appendChild(script)
    // script无法执行
    document.body.innerHTML = script

    动态样式

    
    function loadStyleString(css){
        let style = document.createElement("style");
        style.type = "text/css";
        style.appendChild(document.createTextNode(css));
        let head = document.getElementsByTagName("head")[0];
        head.appendChild(style);
    }

    NodeList

    NodeList和HTMLCollection很相似,但是HTMLCollection对象有额外的namedItem方法,可以通过元素的name获取引用

    MutationObserver

    MutationObserver,可以观察整个文档,或者DOM的一部分,或者某个元素。当发生变化时,会异步的触发callback

    基本使用

    // mo不会关联任何DOM对象,而是需要使用observer
    // 关联具体的dom对象已经需要观察的属性
    const mo = new MutationObserver(() => {
        console.log('dom change')
    })
    // 观察document.body元素,属性的变化。当body的属性发生变化,就会触发MutationObserver中的callback
    // 但是document.body子代的变化,或者docuemnt.body其他内容的变化,不会触发callback, 
    mo.observer(document.body, {
        attributes: true
    })

    MutationRecord数组

    MutationRecord数组记录了一组信息,以为回调的触发可能是多个操作引起的。每个信息里包含了发生了那些变化,影响到那些元素等。

    const div = document.getElementById('id1');
    const mo = new MutationObserver((MRecord) => {
        // 打印结果如下
        console.log(MRecord);
    });
    mo.observe(div, {
        attributes: true,
    });
    div.dataset.name = 'World!';

    「读书笔记」第四版JavaScript高级程序设计(第十四章)_第1张图片

    多次的修改MutationRecord数组中会有多个内容

    
    const div = document.getElementById('id1');
    const mo = new MutationObserver((MRecord) => {
        console.log(MRecord);
    });
    mo.observe(div, {
        attributes: true,
    });
    div.dataset.name = 'World!';
    div.dataset.name = 'Hello World!';

    「读书笔记」第四版JavaScript高级程序设计(第十四章)_第2张图片

    callback

    callback的第一个参数是MutationRecord的数组,第二个参数是MutationObserver的实例

    disconnect方法

    调用disconnect方法,会停止监听回调。

    // 停止监听回调
    mo.disconnect()

    MutationObserver复用

    多次调用observe方法,可以观察多个节点。

    
    const div1 = document.getElementById('id1');
    const div2 = document.getElementById('id2');
    const mo = new MutationObserver((MRecord) => {
        console.log(MRecord);
    });
    mo.observe(div1, {
        attributes: true,
    });
    mo.observe(div2, {
        attributes: true,
    });
    div1.dataset.name = '1';
    div2.dataset.name = '2';

    「读书笔记」第四版JavaScript高级程序设计(第十四章)_第3张图片

    MutationObserver重用

    调用disconnect不会终止MutationObserver实例的生命,我们可以重新调用observe,并继续使用它

    
    const div1 = document.getElementById('id1');
    const div2 = document.getElementById('id2');
    const mo = new MutationObserver((MRecord) => {
        // MRecord数组只有一个内容
        console.log(MRecord);
    });
    mo.observe(div1, {
        attributes: true,
    });
    div1.dataset.name = '1';
    mo.disconnect();
    mo.observe(div2, {
        attributes: true,
    });
    div2.dataset.name = '2';

    MutationObserverInit与观察范围

    MutationObserver可以观察属性的变化,子节点的变化,和文本的变化

    • subtree,boolean,是否观察子代
    • attributes, boolean,是否观察属性
    • attributeFilter,string[], 具体观察那些属性的变化
    • attributeOldValue,boolean, 在MutationRecord数组中是否记录之前的属性值。
    • characterData,boolean,是否观察修改字符数据的变化(文本节点,注释节点)
    • characterDataOldValue, boolean, 在MutationRecord数组中是否记录之前的字符值。
    • childList,boolean, 修改子节点是否触发观察
    subtree, 设置为true,观察整个子数。如果将后代移除子树后,脱离了原来的子树后,对其修改,依然会触发变化。

    childList, 设置为true,只观察一级子树

    异步回调和记录队列

    每一次的变化信息会记录在MutationRecord实例中,然后添加到记录队列中,这个队列是每个MutationObserver实例唯一的。每一次MutationRecord被添加到队列中之后,如果当前微任务队列的长度为0,才会触发之前注册的回调。

    takeRecords

    清空队列,并返回队列中的内容。同时会停止回调监听

    
    // 不会触发console
    let observer = new MutationObserver((mutationRecords) => console.log(mutationRecords));
    
    observer.observe(document.body, { attributes: true });
    
    document.body.className = 'foo';
    document.body.className = 'bar';
    document.body.className = 'baz';
    // [MutationRecord, MutationRecord, MutationRecord]
    console.log(observer.takeRecords());
    // []
    console.log(observer.takeRecords());

    性能、内存与垃圾回收

    MutationObserver对目标DOM是弱引用。如果目标节点被垃圾回收,MutationObserver不会阻碍回收。

    目标节点对MutationObserver是强引用,如果目标节点被回收,MutationObserver也会被回收。

    MutationRecord实例会阻碍节点被垃圾回收。可以提取出MutationRecord有用的信息,保存到新对象中。

    参考

你可能感兴趣的