js学习笔记----JavaScript中DOM扩展的那些事

什么都不说,先上总结的图~

 

 

Selectors API(选择符API)

querySelector()方法

接收一个css选择符,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回null.

//取得 body 元素
var body = document.querySelector("body");

//取得 ID 为"myDiv"的元素
var myDiv = document.querySelector("#myDiv");

//取得类为"selected"的第一个元素
var selected = document.querySelector(".selected");

//取得类为"button"的第一个图像元素
var img = document.body.querySelector("img.button");

通过Document类型调用querySelector()时,会在文档元素的范围内查找匹配的元素

通过Element类型调用querySelector()时,只会在该元素后代元素的范围内查找匹配的元素.

如果传入不被支持的css选择符,querySelector()会抛出错误.

querySelectorAll()方法

querySelectorAll()参数也是一个CSS选择符,返回的是所匹配的元素而不是仅仅一个元素,这个方法返回的是一个NodeList实例.如果没有找到匹配的元素,NodeList就是空的.

具体的说,返回的值实际上是带有所有属性和方法的NodeList,而底层实现则类似于一组元素的快照,而非不断对文档进行搜索的动态查询.这样可以避免使用NodeList对象通常会引起的大多数性能问题.

//取得某<div>中的所有<em>元素(类似于 getElementsByTagName("em"))
var ems = document.getElementById("myDiv").querySelectorAll("em");

//取得类为"selected"的所有元素
var selecteds = document.querySelectorAll(".selected");

//取得所有<p>元素中的所有<strong>元素
var strongs = document.querySelectorAll("p strong");

matchesSelector()方法

Selectors API Level 2规范为Element类型新增了这个方法,该方法接收一个参数,即CSS选择符,如果调用元素和该选择符匹配,返回true;否则返回false

if (document.body.matchesSelector("body.page1")){
    //true
}

在取得某个元素引用的情况下,使用这个方法能够方便的检测它是否会被querySelector()或querySelectorAll()方法返回.

 

元素遍历

对于元素间的空格, IE9 及之前版本不会返回文本节点,而其他所有浏览器都会返回文本节点。这样,就导致了在使用 childNodes 和 firstChild 等属性时的行为不一致。为了弥补这一差异,而同时又保持 DOM规范不变,Element Traversal 规范新定义了一组属性。
Element Traversal API 为 DOM 元素添加了以下 5 个属性。

  • childElementCount :返回子元素(不包括文本节点和注释)的个数。
  • firstElementChild :指向第一个子元素; firstChild 的元素版。
  • lastElementChild :指向最后一个子元素; lastChild 的元素版。
  • previousElementSibling :指向前一个同辈元素; previousSibling 的元素版。
  • nextElementSibling :指向后一个同辈元素; nextSibling 的元素版。

支持的浏览器为DOM元素添加这些属性,利用这些元素不必担心空白文本节点,可以更方便的查找DOM元素了.如下例子:

之前实现跨浏览器遍历某元素的所有子元素,需要这样写:

var i,
len,
child = element.firstChild;
while(child != element.lastChild){
if (child.nodeType == 1){
//检查是不是元素
processChild(child);
}
child = child.nextSibling;
}

如果使用Element Traversal新增的元素,代码会更简洁:

var i,
len,
child = element.firstElementChild;
while(child != element.lastElementChild){
processChild(child);
//已知其是元素
child = child.nextElementSibling;
}

 

HTML5

与类相关的扩充

getElementsByClassName()方法

通过document对象及所有HTML元素调用该方法.该方法接收一个参数,即一个包含一个或多个类名的字符串,返回带有指定类的所有元素的NodeList.传入多个类的时候,类名的先后顺序不重要.

//取得所有类中包含"username"和"current"的元素,类名的先后顺序无所谓
var allCurrentUsernames = document.getElementsByClassName("username current");

//取得 ID 为"myDiv"的元素中带有类名"selected"的所有元素
var selected = document.getElementById("myDiv").getElementsByClassName("selected");

使用这个方法课可以更方便的为带有某些类的元素添加事件处理程序,从而不局限于ID或者标签名,需要注意的是,因为返回的对象是NodeList,所以这个方法与使用getElementsByTagName()以及其他返回NodeList的DOM方法都具有同样的性能问题.

classList属性

在操作类名时通过className属性添加删除或者替换类名,因为className是一个字符串,所以即使修改字符串的一部分,也必须每次都设置整个字符串的值.

//删除"user"类
//首先,取得类名字符串并拆分成数组
var classNames = div.className.split(/\s+/);
//找到要删的类名
var pos = -1,
i,
len;
for (i=0, len=classNames.length; i < len; i++){
if (classNames[i] == "user"){
pos = i;
break;
}
}
//删除类名
classNames.splice(i,1);
//把剩下的类名拼成字符串并重新设置
div.className = classNames.join(" ");

这种处理className的方式是低效且繁琐的,HTML5中新增了一种操作类名的方式,就是为所有元素添加classList属性,这个classList属性是新集合类型DOMTokenList的实例.这个新类型还定义了如下方法:

  • add(value) :将给定的字符串值添加到列表中。如果值已经存在,就不添加了。
  • contains(value) :表示列表中是否存在给定的值,如果存在则返回 true ,否则返回 false 。
  • remove(value) :从列表中删除给定的字符串。
  • toggle(value) :如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。
//删除"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 (var i=0, len=div.classList.length; i < len; i++){
doSomething(div.classList[i]);
}

焦点管理

document.activeElement属性

这个属性始终会引用DOM中当前获得了焦点的元素,元素获得焦点的方式有页面加载,用户输入(键入Tab),在代码中调用focus()方法

var button = document.getElementById("myButton");
button.focus();
alert(document.activeElement === button);
//true

默认情况下,文档在刚刚记载完成时,document.activeElement中保存的是document.body元素的引用.文档在加载期间,document.activeElement的值为null.

document.hasFocus()方法

这个方法用于确定文档是否获得了焦点,通过检测文件是否获得了焦点,可以获得用户是否正在与页面交互

var button = document.getElementById("myButton");
button.focus();
alert(document.hasFocus()); //true

HTMLDocument的扩充

readyState属性

Document的readyState属性有两个可能的值:

  • loading ,正在加载文档;
  • complete ,已经加载完文档。

 通过document.readyState来实现一个指示文档已经加载完成的指示器.在这之前,我们想要实现一个指示器,必须借助onload之间处理程序设置一个标签,表明文档已经加载完毕,document.readyState属性的基本用法如下:

if (document.readyState == "complete"){
//执行操作
}

兼容模式

IE6开始区分渲染页面的模式是标准的还是混杂的,检测页面的兼容模式就成为浏览器的必要功能.IE为此给document添加了一个名为compatMode的属性,这个属性就是为了告诉开发人员浏览器采用哪种渲染模式.在标准模式下,document.compatMode的值为"CSS1Compat",而在混杂模式下,document.compatMode的值等于"BackCompat".

if (document.compatMode == "CSS1Compat"){
alert("Standards mode");
} else {
alert("Quirks mode");
}

head属性

HTML5新增了document.head属性,用来引用文档的<head>元素,如果要引用文档的<head>元素,可以结合使用这个属性和另一种后备方法

var head = document.head || document.getElementsByTagName("head")[0];

 

字符集属性

charset属性

charset属性表示文档中实际使用的字符集,也可以用来指定新字符集.默认情况下这个属性的值是"UTF-16",可以通过<meta>元素,响应头部或直接设置charset属性修改这个值

alert(document.charset); //"UTF-16"
document.charset = "UTF-8";

defaultCharset属性

该属性表示根据默认浏览器及操作系统的设置,当前文档默认的字符集应该是什么样,如果文档没有使用默认的字符集,那么charset和defaultCharset属性的值可能不一样

if (document.charset != document.defaultCharset){
    alert("Custom character set being used.");
}

自定义数据属性

HTML5规定可以为元素添加非标准的属性,但是要添加前缀data-,目的是为元素提供与渲染无关的信息,或者提供语义信息,这些属性可以任意添加,随便命名,只要以data-开头即可.

<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>

dataset属性

添加了自定义属性之后,可以通过元素的dataset属性来访问自定义属性的值.dataset属性的值是DOMStringMap的一个实例,也就是一个名值对的映射.在映射中,每个data-name形式的睡醒都会有一个对应的属性,只不过属性名没有data-前缀.如下例所示:

var div = document.getElementById("myDiv");

//取得自定义属性的值
var appId = div.dataset.appId;
var myName = div.dataset.myname;

//设置值
div.dataset.appId = 23456;
div.dataset.myname = "Michael";

//有没有"myname"值呢?
if (div.dataset.myname){
    alert("Hello, " + div.dataset.myname);
}

如果需要给元素添加一些不可见的数据以便进行其他处理,那就要用到自定义数据属性。在跟踪链接或混搭应用中,通过自定义数据属性能方便地知道点击来自页面中的哪个部分。

插入标记

通过使用插入标记的技术,直接插入HTML字符串不仅更简单,速度也更快.以下与插入标记相关的DOM扩展已经纳入了HTML5规范.

innerHTML属性

读模式:innerHtml属性返回与调用元素的所有子节点(包括注释,元素,文本节点)对应的HTML标记

写模式:innerHTML会根据指定的值创建新的DOM树,然后用这个DOM树完全替换调用元素原先的所有子节点,如下例:

<div id="content">
<p>This is a <strong>paragraph</strong> with a list following it.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
对于上面的 <div> 元素来说,它的 innerHTML 属性会返回如下字符串。
<p>This is a
<ul>
<li>Item
<li>Item
<li>Item
</ul>

需要注意的是,不同浏览器返回的文本格式会有所不同,所以不要指望所有浏览器返回的innerHTML值完全相同.

在写模式下,innerHTML的值会被解析为DOM子树,替换调用元素原来所有的子节点,因为它的值被认为是HTML,所以其中的所有标签都会按照浏览器处理HTML的标准方式转换为元素,如果设置的值仅是文本而没有HTML标签,那么结果就是设置纯文本.

为 innerHTML 设置 HTML 字符串后,浏览器会将这个字符串解析为相应的 DOM树。因此设置了 innerHTML 之后,再从中读取 HTML 字符串,会得到与设置时不一样的结果。原因在于返回的字符串是根据原始 HTML 字符串创建的 DOM 树经过序列
化之后的结果。

使用 innerHTML 属性也有一些限制。比如,在大多数浏览器中,通过 innerHTML 插入 <script>元素并不会执行其中的脚本。

大多数浏览器都支持以直观的方式通过 innerHTML 插入 <style> 元素,如下所示:

div.innerHTML = "<style type=\"text/css\">body {background-color: red; }</style>";

还需要注意的是,并不是所有的元素都支持innerHTML属性,不支持innerHTML的元素有:<col> 、 <colgroup> 、<frameset> 、 <head> 、 <html> 、 <style> 、 <table> 、 <tbody> 、 <thead> 、 <tfoot> 和 <tr> 。

所以无论什么时候,只要使用innerHTML从外部插入HTML,都应该首先以可靠的方式处理HTML.

outerHTML属性

读模式:outerHTML返回调用它的元素及所有子节点的HTML标签

写模式:outerHTML会根据指定的HTML字符串创建新的DOM子树,然后用这个DOM子树完全替换调用元素.

insertAdjacentHTML()方法

该方法接收两个参数:插入位置和要插入的HTML文本,其中第一个参数是下列值之一:

  • "beforebegin" ,在当前元素之前插入一个紧邻的同辈元素;
  • "afterbegin" ,在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素;
  • "beforeend" ,在当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素;
  • "afterend" ,在当前元素之后插入一个紧邻的同辈元素。

这些值必须小写,第二个参数是一个HTML字符串,如果浏览器无法解析该字符串,就会抛出错误.

//作为前一个同辈元素插入
element.insertAdjacentHTML("beforebegin", "<p>Hello world!</p>");

//作为第一个子元素插入
element.insertAdjacentHTML("afterbegin", "<p>Hello world!</p>");

//作为最后一个子元素插入
element.insertAdjacentHTML("beforeend", "<p>Hello world!</p>");

//作为后一个同辈元素插入
element.insertAdjacentHTML("afterend", "<p>Hello world!</p>");

内存与性能问题

在删除带有事件处理程序或引用了其他 JavaScript 对象子树时,就有可能导致内存占用问题。假设某个元素有一个事件处理程序(或者引用了一个 JavaScript 对象作为属性),在使用前述某个属性将该元素从文档树中删除后,元素与事件处理程序(或 JavaScript 对象)之间的绑定关系在内存中并没有一并删除。如果这种情况频繁出现,页面占用的内存数量就会明显增加。因此,在使用 innerHTML 、outerHTML 属性和 insertAdjacentHTML() 方法时,最好先手工删除要被替换的元素的所有事件处理程序和 JavaScript 对象属性.

在插入大量新 HTML 标记时,使用 innerHTML 属性与通过多次 DOM 操作先创建节点再指定它们之间的关系相比,效率要高得多。
这是因为在设置 innerHTML 或 outerHTML 时,就会创建一个 HTML解析器。这个解析器是在浏览器级别的代码(通常是 C++编写的)基础上运行的,因此比执行 JavaScript快得多。不可避免地,创建和销毁 HTML 解析器也会带来性能损失,所以最好能够将设置 innerHTML或 outerHTML 的次数控制在合理的范围内。

scrollIntoView()方法

HTML5通过该方法作为标准方法,可以在所有HTML元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中.如果给该方法传入true作为参数或不传任何参数,那么窗口滚动之后会让调用元素的顶部与视口顶部尽可能平齐.如果传入false为参数,调用元素会尽可能全部出现在视口中,不过顶部不一定平齐.

//让元素可见
document.forms[0].scrollIntoView();

 

专有扩展

首先我们需要知道什么是专有扩展,很多开发商会向DOM中添加专有扩展,用来弥补功能的不足,专有扩展为Web开发领域提供了很多重要的功能,这些功能最终都在HTML5规范中得到了标准化.即使如此,还有大量的专有扩展没有被HTML5加入标准中,所以在使用的时候它们还是专有扩展.

文档模式

页面的文档模式决定了可以使用什么功能。换句话说,文档模式决定了你可以使用哪个级别的 CSS,可以在 JavaScript 中使用哪些 API,以及如何对待文档类型(doctype)。到了 IE9,总共有以下 4 种文档模式。

  • IE5:以混杂模式渲染页面(IE5 的默认模式就是混杂模式)。IE8 及更高版本中的新功能都无法使用。
  • IE7:以 IE7 标准模式渲染页面。IE8 及更高版本中的新功能都无法使用。
  • IE8:以 IE8 标准模式渲染页面。IE8 中的新功能都可以使用,因此可以使用 Selectors API、更多CSS2 级选择符和某些 CSS3 功能,还有一些 HTML5 的功能。不过 IE9 中的新功能无法使用。
  • IE9:以 IE9 标准模式渲染页面。IE9 中的新功能都可以使用,比如 ECMAScript 5、完整的 CSS3以及更多 HTML5 功能。这个文档模式是最高级的模式。

要强制浏览器以某种模式渲染页面,可以使用 HTTP 头部信息 X-UA-Compatible ,或通过等价的<meta> 标签来设置: 

<meta http-equiv="X-UA-Compatible" content="IE=IEVersion">

注意,这里 IE 的版本( IEVersion )有以下一些不同的值,而且这些值并不一定与上述 4 种文档模式对应。

  • Edge :始终以最新的文档模式来渲染页面。忽略文档类型声明。对于 IE8,始终保持以 IE8 标准模式渲染页面。对于 IE9,则以 IE9 标准模式渲染页面。
  • EmulateIE9 :如果有文档类型声明,则以 IE9 标准模式渲染页面,否则将文档模式设置为 IE5。
  • EmulateIE8 :如果有文档类型声明,则以 IE8 标准模式渲染页面,否则将文档模式设置为 IE5。
  • EmulateIE7 :如果有文档类型声明,则以 IE7 标准模式渲染页面,否则将文档模式设置为 IE5。
  • 9 :强制以 IE9 标准模式渲染页面,忽略文档类型声明。
  • 8 :强制以 IE8 标准模式渲染页面,忽略文档类型声明。
  • 7 :强制以 IE7 标准模式渲染页面,忽略文档类型声明。
  • 5 :强制将文档模式设置为 IE5,忽略文档类型声明。

没有规定说必须在页面中设置 X-UA-Compatible 。默认情况下,浏览器会通过文档类型声明来确定是使用最佳的可用文档模式,还是使用混杂模式。 

document.documentMode属性

通过document.documentMode属性可以知道给定页面使用的是什么文档模式,该属性实在IE8之后新增,返回使用的文档模式的版本号.

children属性

该属性是HTMLCollection的实例,只包含元素中同样还是元素的子节点,除此外children属性和childNodes没有什么区别,即在元素中只包含元素子节点时,这两个属性的值相同.

contains()方法

通过该方法可以判断某个节点是不是另一个节点的后代,注意调用contains()方法的应该是祖先节点,也就是搜索开始的节点.

该方法接收一个参数,即要检测的后代节点,如果被检测的节点是后代节点,该方法返回true,否则,返回false.

alert(document.documentElement.contains(document.body)); //true

使用 DOM Level 3 compareDocumentPosition() 也能够确定节点间的关系。这个方法用于确定两个节点间的关系,返回一个表示该关系的位掩码( bitmask)。下表列出了这个位掩码的值。

掩码节点关系
无关(给定的节点不在当前文档中)
居前(给定的节点在DOM树中位于参考节点之前)
居后(给定的节点在DOM树中位于参考节点之后)
包含(给定的节点是参考节点的祖先)
16 被包含(给定的节点是参考节点的后代)

为模仿contains()方法,我们应该关注的是掩码16,对compareDocumentPosition()的结果执行按位与,以确定参考节点.如下例所示:

var result = document.documentElement.compareDocumentPosition(document.body);
alert(!!(result & 16));

通过对浏览器及能力检测,我们可以写一个通用的contains函数:

function contains(refNode, otherNode){
    if (typeof refNode.contains == "function" && (!client.engine.webkit || client.engine.webkit >= 522)) {
        return refNode.contains(otherNode);
    } 
    else if (typeof refNode.compareDocumentPosition == "function") {
        return !!(refNode.compareDocumentPosition(otherNode) & 16);
    } 
    else {
        var node = otherNode.parentNode;

        do {
            if (node === refNode) {
                return true;
            } 
            else {
                node = node.parentNode;
            }
        } while (node !== null);

        return false;
    }
}

 插入文本

innerText属性

通过 innertText 属性可以操作元素中包含的所有文本内容,包括子文档树中的文本。在通过innerText 读取值时,它会按照由浅入深的顺序,将子文档树中的所有文本拼接起来。在通过innerText 写入值时,结果会删除元素的所有子节点,插入包含相应文本值的文本节点。

由于不同浏览器处理空白符的方式不同,因此输出的文本可能会也可能不会包含原始 HTML 代码中的缩进.

注意,设置 innerText 属性移除了先前存在的所有子节点,完全改变了 DOM 子树。此外,设置 innerText属性的同时,也对文本中存在的 HTML 语法字符(小于号、大于号、引号及和号)进行了编码。如下例:

div.innerText = "Hello & welcome, <b>\"reader\"!</b>";

执行上面的一行代码后,会得到下面的结果:

<div id="content">Hello &amp; welcome, &lt;b&gt;&quot;reader&quot;!&lt;/b&gt;</div>

设置 innerText 永远只会生成当前节点的一个子文本节点,而为了确保只生成一个子文本节点,就必须要对文本进行 HTML 编码。利用这一点,可以通过 innerText 属性过滤掉 HTML 标签。方法是将 innerText 设置为等于 innerText ,这样就可以去掉所有 HTML 标签,如:

div.innerText = div.innerText

执行这行代码后,就用原来的文本内容替换了容器元素中的所有内容(包括子节点,因而也就去掉了 HTML 标签)。

支持与innerText作用类似的 textContent 属性。 textContent 是 DOM Level 3 规定的一个属性.

innerText 与 textContent 返回的内容并不完全一样。比如,innerText 会忽略行内的样式和脚本,而 textContent 则会像返回其他文本一样返回行内的样式和脚本代码。避免跨浏览器兼容问题的最佳途径,就是从不包含行内样式或行内脚本的 DOM 子树副本或 DOM 片段中读取文本。

outerText属性

读模式:除了作用范围扩大到了包含调用它的节点之外, outerText 与 innerText 基本上没有多大区别。在读取文本值时, outerText 与 innerText 的结果完全一样。

写模式:outerText 不只是替换调用它的元素的子节点,而是会替换整个元素(包括子节点)。

滚动

HTML5将scrollIntoView()纳入规范后,仍有几个其他专有方法可以在不同的浏览器中使用,下面几个方法都是对HTMLElement类型的扩展,因此在所有的元素中都可调用:

  • scrollIntoViewIfNeeded(alignCenter) :只在当前元素在视口中不可见的情况下,才滚动浏览器窗口或容器元素,最终让它可见。如果当前元素在视口中可见,这个方法什么也不做。如果将可选的 alignCenter 参数设置为 true ,则表示尽量将元素显示在视口中部(垂直方向).Safari 和 Chrome 实现了这个方法。
  • scrollByLines(lineCount) :将元素的内容滚动指定的行高, lineCount 值可以是正值,也可以是负值。Safari 和 Chrome 实现了这个方法。
  • scrollByPages(pageCount) :将元素的内容滚动指定的页面高度,具体高度由元素的高度决定。Safari 和 Chrome 实现了这个方法。
posted @ 2018-09-16 18:24  如是说  阅读(739)  评论(2编辑  收藏  举报