DOM

155 阅读23分钟

节点类型

文档对象模型(DOM)是HTML和XML文档的编程接口,表示由多层节点构成的文档,通过它开发者可以添加、删除和修改页面的各个部分。

DOM现在是真正跨平台、语言无关的表示和操作网页的方式

document节点表示每个文档的根节点。根节点的唯一子节点是<html>元素,我们称之为文档元素(documentElement)。

每个文档只能有一个文档元素。在HTML页面中,文档元素始终是<html>元素

HTML中的每段标记都可以表示为这个树形结构中的一个节点。

DOM中总共有12种节点类型,这些类型都继承一种基本类型(Node类型)。

思考一个问题,DOM节点和元素节点有什么区别?

Node类型

这个接口是所有DOM节点类型都必须实现的。在JS中,所有节点类型都继承Node类型,因此所有类型都共享相同的基本属性和方法

每个节点都有nodeType属性,表示该节点的类型。

节点类型由定义在Node类型上的12个数值常量表示,浏览器并不支持所有节点类型。开发者最常用到的是元素节点和文本节点。

d.jpg

文档中的所有节点都与其他节点有关系

  • 每个节点都有一个childNodes属性,其中包含一个NodeList的实例。

    NodeList是一个类数组对象,用于存储可以按位置存取的有序节点。我们通常说NodeList是实时的活动对象,而不是第一次访问时所获得内容的快照。

    childNodes列表中的每个节点都是同一列表中其他节点的同胞节点。而使用previousSiblingnextSibling可以在这个列表的节点间导航。firstChildlastChild分别指向第一个和最后一个子节点

    hasChildNodes()可以判断节点是否有子节点

    ownerDocument属性是一个指向代表整个文档的文档节点的指针

  • 每个节点都有一个parentNode属性,指向其DOM树中的父元素。

因为所有关系指针都是只读的,所以DOM又提供了一些操纵节点的方法:

  • appendChild(newNode)

    用于在childNodes列表末尾添加节点

    如果把文档中已经存在的节点传给appendChild(),则这个节点会从之前的位置被转移到新位置

  • insertBefor(insertNode, referNode)

    把节点放到childNodes中的特定位置。

    调用这个方法后,要插入的节点会变成参照节点的前一个同胞节点,并被返回

    如果参照节点是null,则insertBefor()appendChild()效果相同

  • replaceChild(insertNode, replaceNode)

    替换节点,要替换的节点会被返回并从文档树中完全移除,要插入的节点会取而代之

  • removeChild(node)

    移除节点,并将其作为返回值

如果在不支持子节点的节点上调用这些方法,则会导致抛出错误。

所有节点类型还共享了两个方法:

  • cloneNode(isCloneDeep: boolean);

    会返回与调用它的节点一样的节点,isCloneDeep表示是否深复制,如果传入false,则只会复制调用该方法的节点,否则复制节点及其整个子DOM树

  • normalize()

    处理文档子树中的文本节点。

    由于解析器实现的差异或DOM操作等原因,可能会出现并不包含文本的文本节点,或者文本节点之间互为同胞关系。在节点上调用normalize()方法会检测这个节点的所有后代,从中搜索上述两种情形。

    如果发现空文本节点,则将其删除;如果两个同胞节点是相邻的,则将其合并为一个文本节点。

Document类型

Document类型是JS中表示文档节点的类型。

在浏览器中,文档对象documentHTMLDocument的实例(继承Document),表示整个HTML页面。

document对象可用于获取关于页面的信息以及操纵其外观和底层结构

documentElement属性始终指向HTML页面中的<html>元素

作为HTMLDocument的实例,document对象还有一个body属性,直接指向<body>元素。

所有主流浏览器都支持document.documentElementdocument.body

document作为HTMLDocument的实例,还有一些标准Document对象上所没有的属性。这些属性提供浏览器所加载网页的信息

  • title 通常显示在浏览器窗口或标签页的标题栏
  • URL 包含当前页面的完整URL(地址栏中的URL)
  • domain 包含页面的域名
  • referrer 包含链接到当前页面的那个页面的URL。如果当前页面没有来源,则referrer属性包含空字符串 所有这些信息都可以在请求的HTTP头部信息中获取,只是在JS中通过这几个属性暴露出来而已

在这些属性中,只有domain属性是可以设置的。

因为跨源通信存在安全隐患,所以不同子域的页面间无法通过JS通信。此时,在每个页面上把document.domain设置为相同的值,这些页面就可以访问对方的JS对象了

比如,一个加载自www.wrox.com的页面中包含一个内嵌窗格,其中的页面加载自p2p.wrox.com。如果每个页面都把document.domain设置为wrox.com,那么这两个页面之间就可以通信了。

浏览器对domain属性还有一个限制,即这个属性一旦放松就不能再收紧了。

使用DOM最常见的情形可能就是获取某个或某组元素的引用,然后对他们执行某些操作。

定位元素的方法有:

  • getElementById()

    如果页面中存在多个具有相同ID的元素,则此方法返回在文档中出现的第一个元素

  • getElementsByTagName()

    此方法返回一个HTMLCollection对象,该对象有一个额外的方法namedItem(),可通过标签的name属性取得某一项的引用。

    <img src="img.gif" name="myImg"/>
    let myImg = images.namedItem('myImg');
    // 等同于
    let myImg = images['myImg'];
    
  • getElementsByName()

    此方法最常用于单选按钮,因为同一字段的单选按钮必须具有相同的name属性才能确保把正确的值发送给服务器

  • querySelector()

    返回匹配该模式的第一个后代元素,没有则返回null

    // 获取body元素
    let body = document.querySelector('body');
    // 根据id获取元素
    let div = document.querySelector('#myDiv');
    // 根据class获取元素
    let div = document.querySelector('.class');
    
  • querySelectorAll()

    返回所有匹配的节点,一个NodeList的静态实例。它是一个静态的“快照”,而非“实时”的查询

  • getElementsByClassName()

    此方法暴露在document对象和所有HTML元素上。返回类名中包含相应类的元素的NodeList

    // 取得所有类名中包含'username'和'current'元素
    // 这两个类名的顺序无关紧要
    let allCurrentUsernames = document.getElementsByClassName('username current');
    
  • 特殊集合 document对象上还暴露了几个特殊集合,这些集合也都是HTMLCollection的实例。

    • document.anchors 包含文档中所有带name属性的<a>元素

    • document.applets 包含文档中所有<applet>元素(因为<applet>元素已经不建议使用,所以这个集合已经废弃)

    • document.forms 包含文档中所有<form>元素

    • document.images 包含文档中所有<img>元素

    • document.links 包含文档中所有带href属性的<a>元素

    这些特殊集合始终存在于HTMLDocument对象上,而且与所有HTMLCollection对象一样,其内容也会实时更新以符合当前文档的内容

Element类型

Element对外暴露出访问元素标签名、子节点和属性的能力。

每个元素都有零个或多个属性,通常用于为元素或其内容附加更多信息。

创建元素

可以使用document.createElement()方法创建新元素。

let div = document.createElement('div');
div.id = 'newDiv';
div.className = 'box';

document.body.appendChild(div);

要取得某个元素的子节点和其他后代节点,可以使用元素的getElementsByTagName()方法。会返回当前元素的后代。

let ul = document.getElementById('myList');
let items = ul.getElementsByTagName('li');

与属性相关的DOM方法主要有3个:

  • getAttribute(attrName)

    在使用getAttribute()访问style属性时,返回的是CSS字符串,而在通过DOM对象的属性访问时,style属性返回的是一个对象。

    如果使用getAttribute()访问事件属性,则返回的是字符串形式的源代码。而通过DOM对象的属性访问事件属性时返回的则是一个JS函数。

    考虑到以上差异,开发者在进行DOM编程时通常会放弃使用getAttribute()而只使用对象属性。

    getAttribute()主要用于取得自定义属性的值

  • setAttribute(name, val)

    如果属性已经存在,则setAttribute()会以指定的值替换原来的值。

    如果属性不存在,则setAttribut()会以指定的值创建该属性。

    适用于HTML属性,也适用于自定义属性。另外,使用该方法设置的属性名会规范为小写形式

    在DOM对象上添加自定义属性,不会自动让它变成元素的属性

    div.mycolor = 'red';
    console.log(div.getAttribute('mycolor')); // null(IE除外)
    
  • removeAttribute(name)

    把属性完全从元素中去掉

  • attributes属性

    Element类型是唯一使用attributes属性的DOM节点类型。

    元素的每个属性都表示为一个Attr节点,并保存在这个NamedNodeMapattributes属性包含一个NamedNodeMap实例)对象中。NamedNodeMap对象包含下列方法:

    • getNamedItem(name) 返回nodeName属性等于name的节点
    • removeNamedItem(name) 删除nodeName属性等于name的节点
    • setNamedItem(node) 向列表中添加node节点,以其nodeName为索引
    • items(pos) 返回索引位置pos处的节点

    attributes属性中的每个节点的nodeName是对应属性的名字,nodeValue是属性的值

    // 取得元素id属性的值
    let id = element.attributes.getNamedItem('id').nodeValue;
    
    // 设置属性的值
    element.attributes['id'].nodeValue = 'someOtherId';
    

    一般来说,因为使用起来更简单,通常开发者更喜欢使用getAttribute()removeAttribute()setAttribute()方法

注意,属性名不区分大小写。另外,根据HTML5规范的要求,自定义属性名应该前缀data-以方便验证。

定义了自定义数据属性后,可以通过元素的dataset属性来访问

let div = document.getElementById('myDiv');
// 取得自定义数据属性的值
let appId = div.dataset.appId;
// 设置自定义数据属性的值
div.dataset.myName = 'echo';
// 判断是否有自定义属性‘myname’
if(div.dataset.myname) {...}

// 属性data-myname、data-myName可以通过myname访问。但是data-my-name、data-My-Name要通过myName来访问

元素的所有属性也可以通过响应DOM元素对象的属性来取得。

Attr 类型

元素数据在DOM中通过Attr类型表示。

Attr类型构造函数和原型在所有浏览器中都可以直接访问

技术上讲,属性是存在于元素attributes属性中的节点。

属性节点尽管是节点,却不被认为是DOM文档树的一部分。

Attr对象上有3个属性: namevaluespecified(表示属性使用的是默认值还是被指定的值)

可以使用document.createAtteibute()方法创建新的Attr节点,参数为属性名

let attr = document.createAttribute('align');
attr.value = 'left';
// 将attr属性添加到element元素上
element.setAttributeNode(attr);

使用attributes属性和getAttributeNode()方法都返回属性对应的Attr节点,而getAttribute()方法只返回属性的值

将属性作为节点来访问多数情况下并无必要。推荐使用getAttribute()removeAttribute()setAttribute()方法操作属性,而不是直接操作属性节点

到此延伸一下prop

prop是从属性对象中取值,不需要在页面中显示定义

对于HTML元素本身就带有的固有属性,在处理时,使用prop方法。快速准确

对于HTML元素自定义的DOM属性,在处理时使用attr方法

<input name="test1" type="checkbox">
<input name="test2" type="checkbox" checked="checked">
$('#test1').attr('checked'); // undefined
$('#test1').prop('checked'); // false

$('#test2').attr('checked'); // 'checked'
$('#test2').prop('checked'); // true

attr获取的是初始化值,除非通过attr('name', 'value')改变,否则值不变

prop属性值是动态的,比如checkbox选中后,checked变为trueprop值也会发生改变

Text类型

Text节点中包含的文本可以通过nodeValue属性访问,也可以通过data属性访问,这两个属性包含相同的值。修改其中一个的值,也会在另一个属性反映出来。

文本节点暴露了以下操作文本的方法:

  • appendData(text) 向节点末尾添加文本text

  • deleteData(offset, count) 从位置offset开始删除count个字符

  • insertData(offset, text) 在位置offset插入text

  • replaceData(offset, count, text)text替换从位置offsetoffset + count的文本

  • splitText(offset) 在位置offset将当前文本节点拆分为两个文本节点

  • substringData(offset, count) 提取从位置offsetoffset + count的文本

默认情况下,包含文本内容的每个元素最多只能有一个文本节点

只要节点在当前的文档树中,相关修改就会马上反映出来。修改文本节点时,HTML或XML代码(取决于文档类型)会被转换成实体编码,即小于号、大于号或引号会被转义

document.createTextNode(str)可以用来创建新文本节点

let textNode = document.createTextNode('<strong>Hello</strong>');

合并相邻的文本节点: normalize()

在包含两个或多个相邻文本节点的父节点上调用normalize()时,所有同胞文本节点会被合并为一个文本节点。

拆分文本节点: splitText()

在指定的偏移位置拆分nodeValue,将一个文本节点拆分成两个文本节点。

拆分文本节点最常用于从文本节点中提取数据的DOM解析技术

Comment类型

DOM中的注释通过Comment类型表示。

可以使用document.createComment()方法创建注释节点

CDATASection类型

CDATASection类型表示XML中特有的CDATA区块。

CDATA区块只在XML文档中有效,因此某些浏览器比较陈旧的版本会错误地将CDATA区块解析为Comment或Element

DocumentFragment 类型

所有节点类型中,DocumentFragment类型是唯一一个在标记中没有对应表示的类型。

DOM将文档片段定义为“轻量级”文档,能够包含和操作节点,却没有完整文档那样额外的消耗

不能直接把文档片段添加到文档。相反,文档片段的作用是充当其他要被添加到文档的节点的仓库

创建文档片段: let fragment = document.createDocumentFragment();

文档片段从Node类型继承了所有文档类型具备的可以执行DOM操作的方法。

如果文档中的一个节点被添加到一个文档片段,则该节点会从文档树中移除,不会再被浏览器渲染

添加到文档片段的新节点同样不属于文档树,不会被浏览器渲染。

可以通过appendChild()insertBefore()方法将文档片段的内容添加到文档。

文档片段本身永远不会被添加到文档树。

回答上面的思考题

元素节点是包含dom节点的,元素节点可以是dom节点、属性节点或者注释节点等。

DOM编程

动态脚本

动态脚本就是在页面初始加载时不存在,之后又通过DOM包含的脚本。

与对应的HTML元素一样,有两种方式通过<script>动态为网页添加脚本:引入外部文件和直接插入源代码

注意, 通过innerHTML属性创建的<script>元素永远不会执行。浏览器会尽责地创建<script>元素,以及其中的脚本文本,但解析器会给这个<script>元素打上永不执行的标签。

只要是使用innerHTML创建的<script>元素,以后也没有办法强制其执行

// 动态加载外部文件
<script src="foo.js"></script>
// 动态创建节点
const script = document.createElement('script');
script.src = 'foo.js';
document.body.appendChild(script);

动态样式

CSS样式在HTML页面中可以通过两个元素加载。<link>元素用于包含CSS外部文件,而<style>元素用于添加嵌入样式。

应该把<link>元素添加到<head>元素而不是<body>元素,这样才能保证所有浏览器都能正常运行。

通过外部文件加载样式是一个异步过程。因此,样式的加载和正执行的js代码并没有先后顺序。一般来说,也没必要知道样式什么时候加载完成。

使用NodeList

理解NodeList对象和相关的NamedNodeMapHTMLCollection,是理解DOM编程的关键。这3个集合类型都是“实时的”,意味着文档结构的变化会实时地在他们身上反映出来,因此他们的值始终代表最新的状态。

任何时候要迭代NodeList,最好再初始化一个变量保存当时查询时的长度,然后用循环变量与这个变量进行比较

let divs = document.getElementsByTagName('div');
for(let i=0, len = divs.length; i< len; ++i) { ... }

// 或者

for(let i=divs.length - 1; i > 0; --i) { ... }

一般来说,最好限制操作NodeList的次数。因为每次查询都会搜索整个文档,所以最好把查询到的NodeList缓存起来。

MutationObserver 接口

此接口可以在DOM被修改时异步执行回调。

使用MutationObserver可以观察整个文档、DOM树的一部分,或者某个元素。此外还可以观察元素属性、子节点、文本或者前三者任意组合的变化

新引进MutationObserver接口是为了取代废弃的MutationEvent

基本用法

let observer = new MutationObserver(() => { ... })

observe(要观察变化的DOM节点,MutationObserverInit对象) 方法

  • MutationObserverInit对象用于控制观察哪些方面的变化,是一个键/值对形式配置选项的字典

新创建的MutationObserver实例不会关联DOM的任何部分。要把这个observer与DOM关联起来,需要使用observe()方法

let observer = new MutationObserver(() => console.log('a'));
// 以下代码配置body元素上任何属性发生变化都会被这个MutationObserver实例发现,然后执行注册的回调函数
observer.observe(document.body, { attributes: true });

document.body.className = 'foo';
console.log('b');

// b
// a

回调与MutationRecord

每个回调都会收到一个MutationRecord实例的数组。

MutationRecord实例包含的信息包括发生了什么变化,以及DOM的哪一部分受到了影响。

因为回调执行之前可能同时发生多个满足观察条件的事件,所以每次执行回调都会传入一个包含按顺序入队的MutationRecord实例的数组

// 传给回调函数的第二个参数是观察变化的MutationObserver的实例
let observer = new MutationObserver((mutationRecords, mutationObserver) => console.log(mutationRecords));
observer.observe(document.body, { attributes: true });
document.body.setAttribute('foo', 'bar');

截图.PNG

连续修改会生成多个MutationRecord实例,下次回调执行时就会收到包含所有这些实例的数组,顺序为变化事件发生的顺序

disconnect() 方法

默认情况下,只要被观察的元素不被垃圾回收,MutationObserver的回调就会响应DOM变化事件,从而被执行。

要提前终止执行回调,可以调用disconnect()方法。

// 同步调用disconnect()之后,不仅会停止此后变化事件的回调,也会抛弃已经假如任务队列要异步执行的回调
let observer = new MutationObserver(() => console.log('a'));
observer.observe(document.body, {attributes: true});
document.body.className = 'foo';
observer.disconnect();
document.bodu.className = 'bar';
// 没有日志输出
// 要想让已经加入任务队列的回调执行,可以使用setTimeout()让已经入列的回调执行完毕再调用disconnect()

复用MutationObserber

多次调用observe()方法,可以复用一个MutationObserver对象观察多个不同的目标节点。此时,MutationRecordtarget属性可以标识发生事件的目标节点。

let observer = new MutationObserver(mutationRecords => console.log(mutationRecords.map(x => x.target)));
// 向页面主体添加两个子节点
let childA = document.createElement('div');
let childB = document.createElement('span');
document.body.appendChild(childA);
document.body.appendChild(childB);

// 观察两个子节点
observer.observe(childA, {attributes: true});
observer.observe(childB, {attributes: true});

// 修改两个子节点的属性
childA.setAttribute('foo', 'bar');
childB.serAttribute('foo', 'bar');

// [<div>, <span>]

// 此时调用disconnect()方法会停止观察所有目标

重用MutationObserver

调用disconnect()并不会结束MutationObserver的生命。还可以重新使用这个观察者,再将它关联到新的目标节点。

MutationObserverInit 与观察范围

MutationObserverInit对象用于控制对目标节点的观察范围。

粗略的讲,观察者可以观察的事件包括属性变化,文本变化和子节点变化

  • subtree 布尔值,表示除了目标节点,是否观察目标节点的后代

    被观察子树中的节点被移除子树之后仍然能够触发变化事件

  • attributes 布尔值,表示是否观察目标节点的属性变化

  • attributeFilter 字符串数组,表示要观察哪些属性的变化

    observer.observe(document.body, {attributeFilter: ['foo']});
    
  • attributeOldValue 布尔值,表示MutationRecord是否记录变化之前的属性值

  • characterData 布尔值,表示修改字符数据是否触发变化事件。默认false

  • characterDataOldValue 布尔值,表示MutationRecord是否记录变化之前的字符数据

  • childList 布尔值,表示修改目标节点的子节点是否触发变化事件。默认false

    对子节点重新排序(尽管调用一个方法即可实现)会报告两次变化事件,因为从技术上会涉及先移除和再添加。

在调用observe()时,MutationObserverInit对象中的attributecharacterDatachildList属性必须至少有一项为true

异步回调与记录队列

MutationObserver接口是出于性能考虑而设计的,其核心是异步回调与记录队列模型。

为了在大量变化事件发生时不影响性能,每次变化的信息(由观察者实例决定)会保存在MutationRecord实例中,然后添加到记录队列。这个队列对每个MutationObserver实例都是唯一的,是所有DOM变化事件的有序列表

记录队列

每次MutationRecord被添加到MutationObserver的记录队列时,仅当之前没有已排期的微任务回调时,才会将观察者注册的回调(在初始化MutationObserver时传入)作为微任务调度到任务队列上。这样可以保证记录队列的内容不会被回调处理两次。

不过在回调的微任务异步执行期间,有可能又会发生更多变化事件。因此被调用的回调会接收到一个MutationRecord实例的数组,顺序为他们进入记录队列的顺序。回调要负责处理这个数组的每一个实例,因为函数退出之后这些实现就不存在了。回调执行后,这些MutationRecord就用不着了,因此记录队列会被清空,其内容会被丢弃。

takeRecords()方法

调用MutationObserver实例的takeRecords()方法可以清空记录队列,取出并返回其中的所有MutationRecord实例。

let observer = new MutationObserver(mutationRecords => console.log(mutationRecords));
observer.observe(document.body, {attributes: true});
document.body.className = 'bar';
document.body.className = 'foo';
document.body.className = 'baz';

console.log(observer.takeRecords());
console.log(observer.takeRecords());

// [MutationRecord, MutationRecord, MutationRecord]
// []

这在希望断开与观察目标的联系,但又希望处理由于调用disconnect()而被抛弃的记录队列中的MutationRecord实例时比较有用。

性能、内存与垃圾回收

MutationEvent定义了一组会在各种DOM变化时触发的事件,由于浏览器事件的实现机制,这个接口出现了严重的性能问题。

因此, DOM Level 3 规定废弃了这些事件,并提供了MutationObserver接口。

将变化回调委托给微任务来执行可以保证事件同步触发,同时避免随之而来的混乱。

但是使用MutationObserver仍然有代价,因此我们需要理解什么时候避免出现这种情况

MutationObserver的引用

MutationObserver拥有对要观察的目标节点的弱引用。因为是弱引用,所以不会妨碍垃圾回收程序回收目标节点。

然而,目标节点却拥有对MutationObserver的强引用。如果目标节点从DOM中被移除,随后被垃圾回收,则关联的MutationObserver也会被垃圾回收

MutationRecord的引用

记录队列中的每个MutationRecord实例至少包含对已有DOM节点的一个引用。如果变化是childList类型,则会包含多个节点的引用。

记录队列和回调处理的默认行为是耗尽这个队列,处理每个MutationRecord,然后让他们超出作用域并被垃圾回收

有时候可能需要保存某个观察者的完整变化记录。保存这些MutationRecord实例,也就会保存他们引用的节点,因而会妨碍这些节点被回收。

如果需要尽快地释放内存,建议从每个MutationRecord中抽取出最有用的信息,然后保存到一个新对象中,最后抛弃MutationRecord

DOM扩展

classList 属性

在没有classList之前。要操作类名,可以通过className属性实现添加、删除和替换。className是一个字符串,所以每次操作之后都需要重新设置这个值才能生效。

<div class="a b c">...</div>
// 删除a类名
let targetClass = 'a';
let classNames = div.className.split(/\s+/);
let idx = classNames.indexOf(targetClass);
if(idx > -1) {
    classNames.splice(idx, 1);
}
div.className = classNames.join(' ');

HTML5通过给所有元素增加classList属性为这些操作提供了更简单也更安全的实现方式

  • add(value) 向类名列表中添加指定的字符串值value。如果这个值已经存在,则什么也不做

  • contains(value) 返回布尔值,表示给定的value是否存在

  • remove(value) 从类名列表中删除指定的字符串值value

    上面的代码可以简化为:div.classList.remove('a');

  • toggle(value) 如果类名列表中已经存在指定的value,则删除,否则添加value

焦点管理

HTML5增加了辅助DOM焦点管理的功能。document.activeElement始终包含当前拥有焦点的DOM元素。

默认情况下,document.activeElement在页面刚加载完之后会设置为document.body。而在页面完全加载之前,document.activeElement的值为null

document.hasFocus()返回布尔值,表示文档是否拥有焦点

readyState 属性

document.readyState属性有两个可能的值

  • loading 表示文档正在加载

  • complete 表示文档加载完成

在实际开发中,最好是把document.readState当成一个指示器,以判断文档是否加载完毕。

innerHTML 属性

在读取innerHTML属性时,会返回元素所有后代的HTML字符串,包括元素、注释和文本节点。

在写入innerHTML时,则会根据提供的字符串值以新的DOM子树替代元素中原来包含的所有节点。

设置innerHTML会导致浏览器将HTML字符串解析为相应的DOM树,并替代元素之前的所有节点。这意味着设置innerHTML属性后马上再读出来会得到不同的字符串。

outerHTML 属性

读取outerHTML属性时,会返回调用它的元素(及所有后代元素)的HTML字符串。

在写入outerHTML属性时,调用它的元素会被传入的HTML字符串经解释之后生成的DOM子树取代。

insertAdjacentHTML() 与 insertAdjacentText()

关于插入标签的最后两个新增方法是insertAdjacentHTML()insertAdjacentText()。第一个参数必须是下列值中的一个:

  • beforebegin 插入当前元素的前面,作为前一个同胞节点

  • afterbegin 插入当前元素内部,作为新的子节点或放在第一个子节点前面

  • beforeend 插入当前元素内部,作为新的子节点或放在最后一个子节点后面

  • afterend 插入当前元素后面,作为下一个同胞节点

第二个参数会作为HTML字符串解析(与innerHTMLouterHTML相同)或作为纯文本解析(与innerTextouterText相同)。如果是HTML,则会在解析出错时抛出错误

// 例如
element.insertAdjacentHTML('afterbegin', '<p>Hello World!</p>');

如果页面中要使用用户提供的信息,则不建议使用innerHTML。与使用innerHTML获得的方便相比,防止XSS攻击更让人头疼。此时,一定要隔离要插入的数据,在插入页面前必须毫不犹豫地使用相关的库对他们进行转义

scrollIntoView()

此方法存在于所有HTML元素上,可以滚动浏览器窗口或容器元素以便包含元素进入视口。

此方法的参数如下:

  • alignToTop 布尔值
    • true 窗口滚动后元素的顶部与视口顶部对齐

    • false 窗口滚动后元素的底部与视口底部对齐

  • scrollIntoViewOptions 选项对象
    • behavior 定义过渡动画,可取值为'smooth''auto',默认'auto'

    • block 定义垂直方向的对齐,可取值为'start''center'、'end'和'nearest',默认'start'

    • inline 定义水平方向的对齐,可取值为'start''center''end''nearest',默认'nearest'

  • 不传参数等同于alignToToptrue
document.forms[0].scrollIntoView();
// 尝试将元素平滑地滚入视口
document.forms[0].scrollIntoView({ behavior: 'smooth', block: 'start' });