Node类型
操纵节点
appendChild()方法返回新添加的节点,如下所示:
Copy let returnedNode = someNode .appendChild (newNode);
alert (returnedNode == newNode); // true
alert ( someNode .lastChild == newNode); // true
// 假设 someNode 有多个子节点
let returnedNode = someNode .appendChild ( someNode .firstChild); alert (returnedNode == someNode .firstChild); // false
alert (returnedNode == someNode .lastChild); // true
如果想把节点放到 childNodes 中的特定位置而不是末尾,则可以使用 insertBefore()方法。 这个方法接收两个参数:要插入的节点和参照节点。调用这个方法后,要插入的节点会变成参照节点的 前一个同胞节点,并被返回。如果参照节点是 null,则 insertBefore()与 appendChild()效果相 同,如下面的例子所示:
Copy // 作为最后一个子节点插入
returnedNode = someNode .insertBefore (newNode , null );
alert (newNode == someNode .lastChild); // true
// 作为新的第一个子节点插入
returnedNode = someNode .insertBefore (newNode , someNode .firstChild); alert (returnedNode == newNode); // true
alert (newNode == someNode .firstChild); // true
// 插入最后一个子节点前面
returnedNode = someNode.insertBefore(newNode, someNode.lastChild); alert(newNode == someNode.childNodes[someNode.childNodes.length - 2]); // true
appendChild()和 insertBefore()在插入节点时不会删除任何已有节点。相对地, replaceChild()方法接收两个参数:要插入的节点和要替换的节点。要替换的节点会被返回并从文档 树中完全移除,要插入的节点会取而代之。下面看一个例子:
Copy // 替换第一个子节点
let returnedNode = someNode .replaceChild (newNode , someNode .firstChild);
// 替换最后一个子节点
returnedNode = someNode .replaceChild (newNode , someNode .lastChild);
要移除节点而不是替换节点,可以使用 removeChild()方法。这个方法接收一个参数,即要移除 的节点。被移除的节点会被返回,如下面的例子所示:
Copy // 删除第一个子节点
let formerFirstChild = someNode .removeChild ( someNode .firstChild);
// 删除最后一个子节点
let formerLastChild = someNode .removeChild ( someNode .lastChild);
cloneNode() 方法用于克隆一个节点,并返回克隆后的节点。该方法可以接受一个布尔值参数,用于指定克隆节点时是否深度克隆其所有子节点。当参数为 true 时,表示深度克隆,即同时克隆该节点的所有子节点;当参数为 false 时,表示浅克隆,即只克隆该节点本身,不包括其所有子节点。下面是 cloneNode(true) 和 cloneNode(false) 的区别:
cloneNode(true):深度克隆节点及其所有子节点。 cloneNode(false):浅克隆节点本身,不包括其所有子节点。
注意 cloneNode()方法不会复制添加到 DOM 节点的 JavaScript 属性,比如事件处理程 序。这个方法只复制 HTML 属性,以及可选地复制子节点。除此之外则一概不会复制。 IE 在很长时间内会复制事件处理程序,这是一个 bug,所以推荐在复制前先删除事件处 理程序。
Document类型
Copy // 取得完整的URL
let url = document . URL ;
// 取得域名
let domain = document .domain;
// 取得来源
let referrer = document .referrer;
URL 跟域名是相关的。比如,如果 document.URL 是 http://www.wrox.com/WileyCDA/,则 document.domain 就是 <www.wrox.com。> 如果 URL 包含子域名如 p2p.wrox.com,则可以将 domain 设置为"wrox.com"(URL 包含“www” 1 时也一样,比如 <www.wrox.com)。不能给这个属性设置> URL 中不包含的值,比如:
Copy // 页面来自p2p.wrox.com
document .domain = "wrox.com" ; // 成功
document .domain = "nczonline.net" ; // 出错!
当页面中包含来自某个不同子域的窗格(<frame>)
或内嵌窗格(<iframe>)
时,设置 document.domain 是有用的。因为跨源通信存在安全隐患,所以不同子域的页面间无法通过 JavaScript 通信。此时,在每个页面上把 document.domain 设置为相同的值,这些页面就可以访问对方的 JavaScript 对象了。比如,一个加载自 <www.wrox.com> 的页面中包含一个内嵌窗格,其中的页面加载自 p2p.wrox.com。这两个页面的 document.domain 包含不同的字符串,内部和外部页面相互之间不能 访问对方的 JavaScript 对象。如果每个页面都把 document.domain 设置为 wrox.com,那这两个页面 之间就可以通信了。 5 浏览器对 domain 属性还有一个限制,即这个属性一旦放松就不能再收紧。比如,把 document.domain 设置为"wrox.com"之后,就不能再将其设置回"p2p.wrox.com",后者会导致错 误,比如:
Copy // 页面来自p2p.wrox.com
document .domain = "wrox.com" ; // 放松,成功 document.domain = "p2p.wrox.com"; // 收紧,错误!
Element类型
Copy if ( element .tagName == "div" ){ // 不要这样做,可能出错! // do something here
}
if ( element . tagName .toLowerCase () == "div" ){ // 推荐,适用于所有文档 // 做点什么
}
HTML 元素:
Copy < div id = "myDiv" class = "bd" title = "Body text" lang = "en" dir = "ltr" ></ div >
这个元素中的所有属性都可以使用下列 JavaScript 代码(对象属性)读取:
Copy let div = document .getElementById ( "myDiv" );
alert ( div .id); // "myDiv"
alert ( div .className); // "bd"
alert ( div .title); // "Body text"
alert ( div .lang); // "en"
alert ( div .dir); // "ltr"
而且,可以使用下列代码修改元素的属性:
Copy div .id = "someOtherId" ;
div .className = "ft" ;
div .title = "Some other text" ;
div .lang = "fr" ;
div .dir = "rtl" ;
```js
### 取得属性
``` js
let div = document .getElementById ( "myDiv" );
alert ( div .getAttribute ( "id" )); // "myDiv"
alert ( div .getAttribute ( "class" )); // "bd"
alert ( div .getAttribute ( "title" )); // "Body text"
alert ( div .getAttribute ( "lang" )); // "en"
alert ( div .getAttribute ( "dir" )); // "ltr"
getAttribute()方法也能取得不是 HTML 语言正式属性的自定义属性的值。比如下面的元素:
Copy < div id = "myDiv" align = "left" my_special_attribute = "hello!" ></ div >
Copy let value = div .getAttribute ( "my_special_attribute" );
注意,属性名不区分大小写,因此"ID"和"id"被认为是同一个属性。另外,根据 HTML5 规范的 要求,自定义属性名应该前缀 data-以方便验证。 因为 id 和 align 在 HTML 中是<div>
元素公认的属性,所以 DOM 对象上也会有这两个属性。但 my_special_attribute 是自定义属性,因此不会成为 DOM 对象的属性。
考虑到以上差异,开发者在进行 DOM 编程时通常会放弃使用 getAttribute()而只使用对象属性。 getAttribute()主要用于取得自定义属性的值。
设置属性
与 getAttribute()配套的方法是 setAttribute(),这个方法接收两个参数:要设置的属性名 和属性的值。如果属性已经存在,则 setAttribute()会以指定的值替换原来的值;如果属性不存在, 11 则 setAttribute()会以指定的值创建该属性。下面看一个例子:
Copy div .setAttribute ( "id" , "someOtherId" );
div .setAttribute ( "class" , "ft" );
div .setAttribute ( "title" , "Some other text" );
div .setAttribute ( "lang" , "fr" );
div .setAttribute ( "dir" , "rtl" );
setAttribute()适用于 HTML 属性,也适用于自定义属性。另外,使用 setAttribute()方法 设置的属性名会规范为小写形式,因此"ID"会变成"id"。 因为元素属性也是 DOM 对象属性,所以直接给 DOM 对象的属性赋值也可以设置元素属性的值, 如下所示:
Copy div .id = "someOtherId" ;
div .align = "left" ;
注意,在 DOM 对象上添加自定义属性,如下面的例子所示,不会自动让它变成元素的属性:
Copy div .mycolor = "red" ;
alert ( div .getAttribute ( "mycolor" )); // null(IE 除外)
最后一个方法 removeAttribute()用于从元素中删除属性。这样不单单是清除属性的值,而是会 把整个属性完全从元素中去掉,如下所示:
Copy div .removeAttribute ( "class" );
这个方法用得并不多,但在序列化 DOM 元素时可以通过它控制要包含的属性。
DocumentFragment类型
假设想给这个<ul>
元素添加 3 个列表项。如果分 3 次给这个元素添加列表项,浏览器就要重新渲染 3 次页面,以反映新添加的内容。为避免多次渲染,下面的代码示例使用文档片段创建了所有列表项, 然后一次性将它们添加到了<ul>
元素:
Copy < ul id = "myList" ></ ul >
Copy let fragment = document .createDocumentFragment ();
let ul = document .getElementById ( "myList" );
for ( let i = 0 ; i < 3 ; ++ i) { 3 let li = document .createElement ( "li" );
li .appendChild ( document .createTextNode ( `Item ${ i + 1 } ` ));
fragment .appendChild (li);
}
ul .appendChild (fragment);
DOM 编程
动态脚本
Copy function loadScript (url) {
let script = document .createElement ( "script" );
script .src = url;
document . body .appendChild (script);
}
动态样式
Copy
function loadStyles (url){
let link = document .createElement ( "link" );
link .rel = "stylesheet" ;
link .type = "text/css" ;
link .href = url;
let head = document .getElementsByTagName ( "head" )[ 0 ];
head .appendChild (link);
}
另一种定义样式的方式是使用<script>
元素包含嵌入的 CSS 规则,例如:
Copy < style type = "text/css" >
body {
background-color : red ;
}
</ style >
逻辑上,下列 DOM 代码会有同样的效果:
Copy let style = document .createElement ( "style" );
style.type = "text/css"; style.appendChild(document.createTextNode("body{background-color:red}")); let head = document.getElementsByTagName["head"](0); head.appendChild(style);
MutationObserver接口
MutationObserver 接口可以在 DOM 被修改时异步执行回调。使 用 MutationObserver 可以观察整个文档、DOM 树的一部分,或某个元素。此外还可以观察元素属性、子节点、文本,或者前三者任意组合的变化。
MutationObserver 的实例要通过调用 MutationObserver 构造函数并传入一个回调函数来创建:
Copy let observer = new MutationObserver (() => console .log ( 'DOM was mutated!' ));
observe()方法
新创建的 MutationObserver 实例不会关联 DOM 的任何部分。要把这个 observer 与 DOM 关 联起来,需要使用 observe()方法。这个方法接收两个必需的参数:要观察其变化的 DOM 节点,以及 一个 MutationObserverInit 对象。
MutationObserverInit 对象用于控制观察哪些方面的变化,是一个键/值对形式配置选项的字典。 例如,下面的代码会创建一个观察者(observer)并配置它观察<body>
元素上的属性变化:
Copy let observer = new MutationObserver (() => console .log ( '<body> attributes changed' ));
observer .observe ( document .body , { attributes : true });
执行以上代码后,<body>
元素上任何属性发生变化都会被这个 MutationObserver 实例发现,然 后就会异步执行注册的回调函数。<body>
元素后代的修改或其他非属性修改都不会触发回调进入任务 队列。可以通过以下代码来验证:
Copy let observer = new MutationObserver (() => console .log ( '<body> attributes changed' ));
observer .observe ( document .body , { attributes : true });
document . body .className = 'foo' ;
console .log ( 'Changed body class' ); 9
// Changed body class
// <body> attributes changed
注意,回调中的 console.log()是后执行的。这表明回调并非与实际的 DOM 变化同步执行。
回调与 MutationRecord
每个回调都会收到一个 MutationRecord 实例的数组。MutationRecord 实例包含的信息包括发 生了什么变化,以及 DOM 的哪一部分受到了影响。因为回调执行之前可能同时发生多个满足观察条件 的事件,所以每次执行回调都会传入一个包含按顺序入队的 MutationRecord 实例的数组。 下面展示了反映一个属性变化的 MutationRecord 实例的数组:
Copy let observer = new MutationObserver (
(mutationRecords) => console .log (mutationRecords)); observer .observe ( document .body , { attributes : true });
document . body .setAttribute ( 'foo' , 'bar' );
// [
// {
// addedNodes: NodeList [],
// attributeName: "foo",
// attributeNamespace: null,
// nextSibling: null,
// oldValue: null,
// previousSibling: null
// removedNodes: NodeList [],
// target: body
// type: "attributes"
// }
// ]
下面是一次涉及命名空间的类似变化:
Copy
let observer = new MutationObserver (
(mutationRecords) => console .log (mutationRecords));
observer .observe ( document .body , { attributes : true });
document . body .setAttributeNS ( 'baz' , 'foo' , 'bar' );
// [
// {
// addedNodes: NodeList [],
// attributeName: "foo",
// attributeNamespace: "baz",
// nextSibling: null,
// oldValue: null,
// previousSibling: null
// removedNodes: NodeList [],
// target: body
// type: "attributes"
// }
// ]
连续修改会生成多个 MutationRecord 实例,下次回调执行时就会收到包含所有这些实例的数组, 顺序为变化事件发生的顺序:
Copy 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]
传给回调函数的第二个参数是观察变化的 MutationObserver 的实例,演示如下:
Copy let observer = new MutationObserver (
(mutationRecords , mutationObserver) => console .log (mutationRecords ,
mutationObserver));
observer .observe ( document .body , { attributes : true }); document . body .className = 'foo' ;
// [MutationRecord], MutationObserver
disconnect()方法 默认情况下,只要被观察的元素不被垃圾回收,MutationObserver 的回调就会响应 DOM 变化事 件,从而被执行。要提前终止执行回调,可以调用 disconnect()方法。下面的例子演示了同步调用 disconnect()之后,不仅会停止此后变化事件的回调,也会抛弃已经加入任务队列要异步执行的回调:
Copy let observer = new MutationObserver (() => console .log ( '<body> attributes changed' ));
observer .observe ( document .body , { attributes : true });
document . body .className = 'foo' ;
observer .disconnect ();
document . body .className = 'bar' ;
//(没有日志输出)
要想让已经加入任务队列的回调执行,可以使用 setTimeout()让已经入列的回调执行完毕再调用 disconnect():
Copy let observer = new MutationObserver (() => console .log ( '<body> attributes changed' ));
observer .observe ( document .body , { attributes : true });
document . body .className = 'foo' ;
setTimeout (() => {
observer .disconnect ();
document . body .className = 'bar' ;
} , 0 );
// <body> attributes changed
复用 MutationObserver
多次调用 observe()方法,可以复用一个 MutationObserver 对象观察多个不同的目标节点。此 时,MutationRecord 的 target 属性可以标识发生变化事件的目标节点。下面的示例演示了这个过程:
Copy let observer = new MutationObserver (
(mutationRecords) => console .log ( mutationRecords .map ((x) =>
x .target)));
// 向页面主体添加两个子节点
let childA = document .createElement ( 'div' ) ,
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 .setAttribute ( 'foo' , 'bar' );
// [<div>, <span>]
disconnect ()方法是一个“一刀切”的方案,调用它会停止观察所有目标:
重用 MutationObserver
调用 disconnect()
并不会结束 MutationObserver
的生命。还可以重新使用这个观察者,再将 它关联到新的目标节点。下面的示例在两个连续的异步块中先断开然后又恢复了观察者与<body>
元素 的关联:
Copy
let observer = new MutationObserver (() => console .log ( '<body> attributes changed' ));
observer .observe ( document .body , { attributes : true });
document . body .setAttribute ( 'foo' , 'bar' )
setTimeout (() => {
observer .disconnect ();
// 这行代码不会触发变化事件
document . body .setAttribute ( 'bar' , 'baz' );
} , 0 );
setTimeout (() => {
// Reattach
observer .observe ( document .body , { attributes : true });
// 这行代码会触发变化事件
document . body .setAttribute ( 'baz' , 'qux' );
} , 0 );
// <body> attributes changed
// <body> attributes changed
takeRecords()方法
调用 MutationObserver 实例的 takeRecords()方法可以清空记录队列,取出并返回其中的所 4 有 MutationRecord 实例。看这个例子:
Copy 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' ;
console .log ( observer .takeRecords ()); console .log ( observer .takeRecords ());
// [MutationRecord, MutationRecord, MutationRecord]
// []
这在希望断开与观察目标的联系,但又希望处理由于调用 disconnect()而被抛弃的记录队列中的 MutationRecord 实例时比较有用。
MutationObserverInit与观察范围
注意 在调用observe()时,MutationObserverInit对象中的attribute、characterData 和childList属性必须至少有一项为true(无论是直接设置这几个属性,还是通过设置 attributeOldValue 等属性间接导致它们的值转换为 true)。否则会抛出错误,因为没 有任何变化事件可能触发回调。
观察属性
MutationObserver 可以观察节点属性的添加、移除和修改。要为属性变化注册回调,需要在 MutationObserverInit 对象中将 attributes 属性设置为 true,如下所示:
Copy let observer = new MutationObserver (
(mutationRecords) => console .log (mutationRecords));
observer .observe ( document .body , { attributes : true });
// 添加属性
document . body .setAttribute ( 'foo' , 'bar' );
// 修改属性
document . body .setAttribute ( 'foo' , 'baz' );
// 移除属性
document . body .removeAttribute ( 'foo' );
// 以上变化都被记录下来了
// [MutationRecord, MutationRecord, MutationRecord]
DOM扩展
matches()
matches()方法(在规范草案中称为 matchesSelector())接收一个 CSS 选择符参数,如果元素 匹配则该选择符返回 true,否则返回 false。例如:
Copy if ( document . body .matches ( "body.page1" )){
// true
}
使用这个方法可以方便地检测某个元素会不会被 querySelector()或 querySelectorAll()方 法返回。 所有主流浏览器都支持 matches()。Edge、Chrome、Firefox、Safari 和 Opera 完全支持,IE9~11 及一些移动浏览器支持带前缀的方法。
getElementsByClassName()
etElementsByClassName()方法接收一个参数,即包含一个或多个类名的字符串,返回类名中 包含相应类的元素的 NodeList。如果提供了多个类名,则顺序无关紧要。下面是几个示例:
Copy // 取得所有类名中包含"username"和"current"元素
// 这两个类名的顺序无关紧要
let allCurrentUsernames = document .getElementsByClassName ( "username current" );
// 取得 ID 为"myDiv"的元素子树中所有包含"selected"类的元素
let selected = document .getElementById ( "myDiv" ) .getElementsByClassName ( "selected" );
classList 属性
Copy < div class = "bd user disabled" >...</ div >
HTML5 通过给所有元素增加 classList 属性为这些操作提供了更简单也更安全的实现方式。 classList 是一个新的集合类型 DOMTokenList 的实例。与其他 DOM 集合类型一样,DOMTokenList 也有 length 属性表示自己包含多少项,也可以通过 item()或中括号取得个别的元素。此外, DOMTokenList 还增加了以下方法。 24
add(value),向类名列表中添加指定的字符串值 value。如果这个值已经存在,则什么也不做。 contains(value),返回布尔值,表示给定的 value 是否存在。
remove(value),从类名列表中删除指定的字符串值 value。
toggle(value),如果类名列表中已经存在指定的 value,则删除;如果不存在,则添加。 这样以来,前面的例子中那么多行代码就可以简化成下面的一行:
Copy div . classList .remove ( "user" );
这行代码可以在不影响其他类名的情况下完成删除。其他方法同样极大地简化了操作类名的复杂 性,如下面的例子所示:
Copy // 删除"disabled"类
div . classList .remove ( "disabled" );
// 添加"current"类
div . classList .add ( "current" );
// 切换"user"类
div . classList .toggle ( "user" );
// 检测类名
if ( div . classList .contains ( "bd" ) && ! div . classList .contains ( "disabled" )){
// 执行操作
}
// 迭代类名
for ( let class of div .classList){
doStuff (class);
}
焦点管理
HTML5 增加了辅助 DOM 焦点管理的功能。首先是 document.activeElement,始终包含当前拥 有焦点的 DOM 元素。页面加载时,可以通过用户输入(按 Tab 键或代码中使用 focus()方法)让某个 元素自动获得焦点。例如:
Copy let button = document .getElementById ( "myButton" );
button .focus ();
console .log ( document .activeElement === button); // true
默认情况下,document.activeElement 在页面刚加载完之后会设置为 document.body。而在 页面完全加载之前,document.activeElement 的值为 null。 其次是 document.hasFocus()方法,该方法返回布尔值,表示文档是否拥有焦点:
Copy let button = document .getElementById ( "myButton" );
button .focus ();
console .log ( document .hasFocus ()); // true
确定文档是否获得了焦点,就可以帮助确定用户是否在操作页面。 第一个方法可以用来查询文档,确定哪个元素拥有焦点,第二个方法可以查询文档是否获得了焦点, 而这对于保证 Web 应用程序的无障碍使用是非常重要的。无障碍 Web 应用程序的一个重要方面就是焦 点管理,而能够确定哪个元素当前拥有焦点(相比于之前的猜测)是一个很大的进步。
HTMLDocument扩展
readyState 属性
document.readyState 属性有两个可能的值:
实际开发中,最好是把 document.readState 当成一个指示器,以判断文档是否加载完毕。在这 个属性得到广泛支持以前,通常要依赖 onload 事件处理程序设置一个标记,表示文档加载完了。这个 属性的基本用法如下:
Copy if ( document .readyState == "complete" ){ // 执行操作
}
自定义数据属性
Copy
< div id = "myDiv" data-appId = "12345" data-myname = "Nicholas" ></ div >
Copy // 本例中使用的方法仅用于示范
let div = document .getElementById ( "myDiv" );
// 取得自定义数据属性的值
let appId = div . dataset .appId;
let myName = div . dataset .myname;
// 设置自定义数据属性的值
div . dataset .appId = 23456 ;
div . dataset .myname = "Michael" ;
// 有"myname"吗?
if ( div . dataset .myname){
console .log ( `Hello, ${ div . dataset .myname } ` );
}
自定义数据属性非常适合需要给元素附加某些数据的场景,比如链接追踪和在聚合应用程序中标识 页面的不同部分。另外,单页应用程序框架也非常多地使用了自定义数据属性。
insertAdjacentHTML()与 insertAdjacentText()
关于插入标签的最后两个新增方法是 insertAdjacentHTML()和 insertAdjacentText()。这两 个方法最早源自 IE,它们都接收两个参数:要插入标记的位置和要插入的 HTML 或文本。第一个参数 必须是下列值中的一个:
"beforebegin",插入当前元素前面,作为前一个同胞节点;
"afterbegin",插入当前元素内部,作为新的子节点或放在第一个子节点前面;
"beforeend",插入当前元素内部,作为新的子节点或放在最后一个子节点后面;
"afterend",插入当前元素后面,作为下一个同胞节点。 注意这几个值是不区分大小写的。第二个参数会作为 HTML 字符串解析(与 innerHTML 和 outerHTML 相同)或者作为纯文本解析(与 innerText 和 outerText 相同)。如果是 HTML,则会 在解析出错时抛出错误。下面展示了基本用法1:
Copy // 作为前一个同胞节点插入
element .insertAdjacentHTML ( "beforebegin" , "<p>Hello world!</p>" );
element .insertAdjacentText ( "beforebegin" , "Hello world!" );
// 作为第一个子节点插入
element .insertAdjacentHTML ( "afterbegin" , "<p>Hello world!</p>" );
element .insertAdjacentText ( "afterbegin" , "Hello world!" );
// 作为最后一个子节点插入
element .insertAdjacentHTML ( "beforeend" , "<p>Hello world!</p>" );
element .insertAdjacentText ( "beforeend" , "Hello world!" );
// 作为下一个同胞节点插入
element .insertAdjacentHTML ( "afterend" , "<p>Hello world!</p>" );
element . insertAdjacentText ( "afterend" , "Hello world!" );
scrollIntoView()
DOM 规范中没有涉及的一个问题是如何滚动页面中的某个区域。为填充这方面的缺失,不同浏览 器实现了不同的控制滚动的方式。在所有这些专有方法中,HTML5 选择了标准化 scrollIntoView()。 scrollIntoView()方法存在于所有 HTML 元素上,可以滚动浏览器窗口或容器元素以便包含元 素进入视口。这个方法的参数如下:
true:窗口滚动后元素的顶部与视口顶部对齐。 false:窗口滚动后元素的底部与视口底部对齐。
scrollIntoViewOptions 是一个选项对象。
behavior:定义过渡动画,可取的值为"smooth"和"auto",默认为"auto"。
block:定义垂直方向的对齐,可取的值为"start"、"center"、"end"和"nearest",默 认为 "start"。
inline:定义水平方向的对齐,可取的值为"start"、"center"、"end"和"nearest",默 认为 "nearest"。
不传参数等同于 alignToTop 为 true。 来看几个例子:
Copy // 确保元素可见
document .forms[ 0 ] .scrollIntoView ();
// 同上
document .forms[ 0 ] .scrollIntoView ( true );
document .forms[ 0 ] .scrollIntoView ({block : 'start' });
// 尝试将元素平滑地滚入视口
document .forms[ 0 ] .scrollIntoView ({behavior : 'smooth' , block : 'start' });
这个方法可以用来在页面上发生某个事件时引起用户关注。把焦点设置到一个元素上也会导致浏览 器将元素滚动到可见位置。
遍历
DOM2 Traversal and Range 模块定义了两个类型用于辅助顺序遍历 DOM 结构。这两个类型—— NodeIterator 和 TreeWalker——从某个起点开始执行对 DOM 结构的深度优先遍历。 如前所述,DOM 遍历是对 DOM 结构的深度优先遍历,至少允许朝两个方向移动(取决于类型)。 遍历以给定节点为根,不能在 DOM 中向上超越这个根节点。来看下面的 HTML:
Copy <! DOCTYPE html >
< html >
< head >
< title >Example</ title >
</ head >
< body >
< p >< b >Hello</ b > world!</ p >
</ body >
</ html >
其中的任何节点都可以成为遍历的根节点。比如,假设以<body>
元素作为遍历的根节点,那么接 下来是<p>
元素、<b>
元素和两个文本节点(都是<body>
元素的后代)。但这个遍历不会到达<html>
元 素、<head>
元素,或者其他不属于<body>
元素子树的元素。而以 document 为根节点的遍历,则可以 访问到文档中的所有节点。图 16-6 展示了以 document 为根节点的深度优先遍历。
NodeIterator
NodeIterator 类型是两个类型中比较简单的,可以通过 document.createNodeIterator()方 法创建其实例。这个方法接收以下 4 个参数。
whatToShow,数值代码,表示应该访问哪些节点。
filter,NodeFilter 对象或函数,表示是否接收或跳过特定节点。
entityReferenceExpansion,布尔值,表示是否扩展实体引用。这个参数在 HTML 文档中没 有效果,因为实体引用永远不扩展
Copy < div id = "div1" >
< p >< b >Hello</ b > world!</ p >
< ul >
< li >List item 1</ li >
< li >List item 2</ li >
< li >List item 3</ li >
</ ul >
</ div >
假设想要遍历
元素内部的所有元素,那么可以使用如下代码:
Copy let div = document .getElementById ( "div1" );
let iterator = document .createNodeIterator (div , NodeFilter . SHOW_ELEMENT , null , false );
let node = iterator .nextNode ();
while (node !== null ) {
console .log ( node .tagName); // 输出标签名
node = iterator .nextNode ();
}
这个例子中第一次调用 nextNode()返回
元素。因为 nextNode()在遍历到达 DOM 子树末 尾时返回 null,所以这里通过 while 循环检测每次调用 nextNode()的返回值是不是 null。以上代 码执行后会输出以下标签名: DIV P B UL LI LI LI 如果只想遍历<li>
元素,可以传入一个过滤器,比如:
Copy let div = document .getElementById ( "div1" );
let filter = function (node) {
return node . tagName .toLowerCase () == "li" ?
NodeFilter . FILTER_ACCEPT :
NodeFilter . FILTER_SKIP ;
};
let iterator = document .createNodeIterator (div , NodeFilter . SHOW_ELEMENT , filter , false );
let node = iterator .nextNode ();
while (node !== null ) {
console .log ( node .tagName);
node = iterator .nextNode ();
}
// 输出标签名 在这个例子中,遍历只会输出<li>
元素的标签。 nextNode()和 previousNode()方法共同维护 NodeIterator 对 DOM 结构的内部指针,因此修 改 DOM 结构也会体现在遍历中。
TreeWalker
TreeWalker 是 NodeIterator 的高级版。除了包含同样的 nextNode()、previousNode()方法, TreeWalker 还添加了如下在 DOM 结构中向不同方向遍历的方法。
parentNode(),遍历到当前节点的父节点。
firstChild(),遍历到当前节点的第一个子节点。
lastChild(),遍历到当前节点的最后一个子节点。
nextSibling(),遍历到当前节点的下一个同胞节点。
previousSibling(),遍历到当前节点的上一个同胞节点。
Copy let div = document .getElementById ( "div1" );
let filter = function (node) {
return node . tagName .toLowerCase () == "li" ?
NodeFilter . FILTER_ACCEPT :
NodeFilter . FILTER_SKIP ;
};
let walker = document .createTreeWalker (div , NodeFilter . SHOW_ELEMENT , filter , false );
let node = iterator .nextNode ();
while (node !== null ) {
console .log ( node .tagName); // 输出标签名
node = iterator .nextNode ();
}
Copy let div = document .getElementById ( "div1" );
let walker = document .createTreeWalker (div , NodeFilter . SHOW_ELEMENT , null , false );
walker .firstChild (); // 前往<p> walker.nextSibling(); // 前往<ul>
let node = walker .firstChild (); // 前往第一个<li>
while (node !== null ) {
console .log ( node .tagName);
node = walker .nextSibling ();
}
DOM 范围
DOM2 在 Document 类型上定义了一个 createRange()方法,暴露在 document 对象上。使用这 个方法可以创建一个 DOM 范围对象,如下所示:
Copy let range = document.createRange();
简单选择
Copy <! DOCTYPE html >
< html >
< body >
< p id = "p1" >< b >Hello</ b > world!</ p >
</ body >
</ html >
以下 JavaScript 代码可以访问并创建相应的范围:
Copy let range1 = document .createRange () ,
range2 = document .createRange () ,
p1 = document .getElementById ( "p1" );
range1 .selectNode (p1);
range2 .selectNodeContents (p1);
例子中的这两个范围包含文档的不同部分。range1 包含<p>
元素及其所有后代,而 range2 包含 <b>
元素、文本节点"Hello"和文本节点" world!",如图 16-7 所示。