原文:blog.logrocket.com/8-dom-featu…
作者:Louis Lazaris
翻译:前端小白

随着最近对工具的关注越来越多,让我们从React和npm-install-everything发布的所有文章中抽点时间来了解一下在现代浏览器中不需要依赖的纯DOM和Web API的特性。
这篇文章将考虑八个鲜为人知的DOM特性,这些特性具有强大的浏览器支持。 为了帮助解释每个工作原理,我会提供大量的交互式演示,同时也便于你自己尝试
这些方法和属性没有一个陡峭的学习曲线,可以很好地配合你的项目所使用的任何工具集。
你肯定已经使用过 addEventListener() 将事件附加到web文档中的元素上。通常,addEventListener() 调用看起来是这样的:
element.addEventListener('click', doSomething, false);
第一个参数是我正在监听的事件。第二个参数是一个回调函数,它将在事件发生时执行。第三个参数是一个名为 useCapture 的布尔值,用于指示是否要使用事件冒泡或捕获。
这些都是众所周知的(尤其是前两个)。但是,您可能不知道 addEventListener() 也接受将第三个布尔值类型的参数替换为另一个新参数。这个新参数是一个看起来像这样的 options 对象:
element.addEventListener('click', doSomething, {
capture: false,
once: true,
passive: false
});
注意,语法允许定义三个不同的属性。下面是每个词的意思:
- capture: 一个布尔值和
useCapture参数一样 - once: 一个布尔值,如果设为
true, 表示事件只在目标元素上运行一次,然后被删除 - passive: 一个布尔值,如果设为
true, 表示函数不会调用preventDefault(), 即使它包含在函数体中
这三个选项中最有趣的是 once。这在很多情况下都非常有用,可以避免使用 removeEventListener() 或其他复杂的技术来强制触发单个事件。如果您曾经使用过jQuery,那么您可能熟悉该库中的类似特性.one()方法。
您可以在下面的codepen中尝试使用options对象:
codepen: codepen.io/impressivew…
注意演示页面上的按钮只会追加文本一次。如果将once值更改为false,然后多次单击该按钮,则每次单击按钮时都会附加文本。
浏览器对options对象的支持非常友好:除了IE11和更早版本之外,所有浏览器都支持它,所以如果你不在乎微软Edge之前的浏览器,那么使用它是非常安全的。
scrollTo() 在窗口或元素中平滑滚动
平滑滚动一直是需要的。当本地页面链接立即跳转到指定位置时(如果你眨眼,你甚至可能会错过跳转),这会让人很不舒服。 平滑滚动改进了用户页面体验。
虽然过去使用jQuery插件可以实现,但现在可以使用 window.scrollTo() 方法,只需要使用一行JavaScript代码就可以实现。
scrollTo() 方法应用于 window 对象,以告诉浏览器滚动到页面上指定的位置。下面是一个基本语法的示例:
window.scrollTo(0, 1000);
这将使窗口向右滚动0px(表示x坐标,或水平滚动),向下滚动1000px(垂直方向,这通常是您想要的)。但在这种情况下,滚动将不会是一个平滑的动画效果;页面将突然滚动,就像使用本地链接到指定哈希URL一样。
虽然有时候这就是你想要的。但是为了获得平滑的滚动,必须使用很少有人知道的 ScrollToOptions 对象,就像这样:
window.scrollTo({
top: 0,
left: 1000,
behavior: 'smooth'
});
这段代码与前面的示例相同,但是在 options 对象中添加了 behavior 属性并将其值设为 smooth。
codepen: codepen.io/impressivew…
关于这个特性的一些注意事项:
- 虽然几乎所有的浏览器都支持
scrollTo(),但不是所有浏览器都支持options对象 - 该方法同样作用于DOM元素上,而不仅仅
window options对象同样适用于scroll()和scrollBy()
可选参数的setTimeout()和setInterval()
在许多情况下,使用 window.setTimeout() 和 window.setInterval() 执行基于时间的动画现在已经被性能更友好的 window.requestAnimationFrame() 所取代。但是在某些情况下,setTimeout() 或 setInterval() 是更好的的选择,因此有必要了解其中很少有人知道的一些特性。
通常,你会看到使用他们的语法是这样的:
let timer = window.setInterval(doSomething, 3000);
function doSomething () {
// Something happens here...
}
这里 setInterval() 调用传入两个参数:回调函数和时间间隔。对于 setTimeout(),它将运行一次,而在本例中,它将无限期地运行,直到我传入计时器ID,调用 window.clearTimeout() 清除定时器。
使用起来很简单。 但是,如果我希望我的回调函数接受参数呢? 最近这些计时器方法允许添加以下内容:
let timer = window.setInterval(doSomething, 3000, 10, 20);
function doSomething (a, b) {
// Something happens here…
}
注意,我在 setInterval() 调用中又添加了两个参数。然后,我的 doSomething() 函数接受这些参数并根据需要操作它们。
以下的codepen演示了如何使用 setTimeout() 实现此功能:
codepen: codepen.io/impressivew…
当单击该按钮时,将执行两个传入值的计算。可以通过页面上的数字输入更改值。
至于浏览器支持,还不是很确定,但似乎所有正在使用的浏览器都支持可选参数功能,包括IE10。
单选按钮和复选框的defaultChecked属性
您可能知道,对于单选按钮和复选框,如果希望获取或设置 checked 属性,可以使用 checked 属性,如下所示(假设 radioButton 是对某个表单输入的引用)
console.log(radioButton.checked); // true
radioButton.checked = false;
console.log(radioButton.checked); // false
但还有一个名为 defaultChecked 的属性,它可以应用于单选按钮或复选框组,来获得组中最初设置为checked的元素是哪个
<form id="form">
<input type="radio" value="one" name="setOne"> One
<input type="radio" value="two" name="setOne" checked> Two<br />
<input type="radio" value="three" name="setOne"> Three
</form>
有了这个,即使在更改了选中的单选按钮之后,我也可以遍历单选框并找出初始为checked是哪一个,如下所示:
for (i of myForm.setOne) {
if (i.defaultChecked === true) {
console.log(‘i.value’);
}
}
下面是CodePen演示,它将显示当前选中的单选框或默认选中的单选框,具体取决于你点击哪个按钮:
codepen: codepen.io/impressivew…
该示例中的 defaultChecked 始终是单选框Two。如前所述,这也可以通过复选框组来完成。尝试更改HTML中的默认选中选项,然后再次尝试点击按钮。
这是复选框组的演示: codepen: codepen.io/impressivew…
在本例中,你将注意到默认情况下选中了两个复选框,因此当使用 defaultChecked 查询时,这两个复选框都将返回 true。
使用 normalize() and wholeText() 操作文本节点
HTML文档中的文本节点可能比较复杂,特别是在动态插入或创建节点时。例如,如果我有以下HTML
<p id="el">This is the initial text.</p>
我可以给p元素增加一个文本节点:
let el = document.getElementById('el');
el.appendChild(document.createTextNode(' Some more text.'));
console.log(el.childNodes.length); // 2
注意,在添加文本节点之后,我将记录该段中子节点的长度,它表示有两个节点。这些节点是一个文本字符串,但是因为文本是动态附加的,所以它们被视为单独的节点。
在某些情况下,如果将文本作为单个文本节点处理将会更有帮助,这使得文本更容易操作。这就是 normalize() 和 wholeText() 发挥作用的地方。
normalize() 方法可用于合并单独的文本节点
el.normalize();
console.log(el.childNodes.length); // 1
对元素调用 normalize() 将合并该元素内的任何相邻文本节点。如果恰好有一些HTML穿插在相邻的文本节点中,HTML将保持原样,而所有相邻的文本节点将被合并。
但是,如果由于某种原因我想保持文本节点分开,但我仍然希望能够将文本作为单个单元抓取,那么这就是 wholeText() 有用的地方。 因此,我可以在相邻的文本节点上执行此操作,而不是调用 normalize():
console.log(el.childNodes[0].wholeText);
// This is the initial text. Some more text.
console.log(el.childNodes.length); // 2
只要我没有调用 normalize(),文本节点的长度将保持为2,并且我可以使用 wholeText() 记录整个文本。但是请注意几点:
- 我必须在一个文本节点上调用
wholeText,而不是元素(el.childNodes[0]或者el.childNodes[1]) - 文本节点必须相邻,中间没有HTML分隔它们
insertAdjacentElement() 和 insertAdjacentText()
许多人可能熟悉 insertAdjacentHTML() 方法,它允许您轻松地将一串文本或HTML添加到页面中与其他元素相关的特定位置。
但是,可能您没有注意到该规范还包含两个相关的方法,它们以类似的方式工作: insertAdjacentElement() 和 insertAdjacentText()
insertAdjacentHTML() 的一个缺点是插入的内容必须是字符串的形式。 因此,如果您包含HTML,则必须将其声明为:
el.insertAdjacentHTML('beforebegin', '<p><b>Some example</b> text goes here.</p>');
但是,使用 insertAdjacentElement(),第二个参数可以是一个元素的引用,如下:
let el = document.getElementById('example'),
addEl = document.getElementById('other');
el.insertAdjacentElement('beforebegin', addEl);
这个方法的有趣之处在于,它不仅将引用的元素添加到指定的位置,而且还将从文档中的原始位置删除元素。因此,这是将一个元素从DOM中的一个位置转移到另一个位置的简单方法。
下面是一个使用 insertAdjacentElement() 的CodePen演示:
codepen: codepen.io/impressivew…
insertAdjacentText() 方法的工作原理类似,但是所提供的文本字符串将仅作为文本插入,即使它包含HTML。demo如下:
codepen: codepen.io/impressivew…
您可以将自己的文本添加到输入字段,然后使用按钮将其添加到文档中。注意,任何特殊字符(如HTML标记)都将作为HTML实体插入,这与 insertAdjacentHTML() 方法的行为不同。
insertAdjacentHTML() , insertAdjacentElement(), 和 insertAdjacentText() 的第一个参数相同,可取值为:
- beforebegin
- afterbegin
- beforeend
- afterend
event.detail 属性
当使用 addEventListener() 时,您可能需要防止函数调用中的默认浏览器行为。例如,您可能想拦截 <a> 元素上的单击,并使用JavaScript处理这些单击。你会这样做:
btn.addEventListener('click', function (e) {
// do something here...
e.preventDefault();
}, false);
preventDefault(),和以前的 return false 效果是一样的。这需要将事件对象传递到函数中,因为该对象会调用 preventDefault() 方法。
你还可以利用那个事件对象做更多。实际上,当触发某些事件(例如click、dbclick、mouseup、mousedown)时,他们会暴露出一个称为UIEvent接口。正如MDN所指出的,这个接口上的许多特性都被废除了。但有用的是detail属性,它是官方规范的一部分。
他看起来像这样:
btn.addEventListener('click', function (e) {
// do something here...
console.log(e.detail);
}, false);
cedepen: codepen.io/impressivew…
演示中的每个按钮都将以按钮文本描述的方式响应,并显示一条显示当前单击次数的消息。需要注意的一些事情:
- WebKit内核浏览器允许无限制的点击次数,dbclick除外,它总是两次。 Firefox只允许最多三次点击,然后计数再次开始
- 我已经包括了
blur和focus,来证明这些不符合条件,并将始终返回0(即没有点击) - IE11这类的旧浏览器可能会有问题
注意,演示中包含了一个很实用的技巧,可以模拟三次单击事件:
btnT.addEventListener('click', function (e) {
if (e.detail === 3) {
trpl.value = 'Triple Click Successful!';
}
}, false);
如果所有浏览器都计算过三次点击次数,那么您还可以检测到更高的点击次数,但我认为在大多数情况下,三次点击事件就足够了。
scrollHeight 和 scrollWidth 属性
scrollHeight 和 scrollWidth 属性可能听起来很熟悉,因为您可能会将它们与其他与宽度和高度相关的DOM功能混淆。 例如,offsetWidth 和 offsetHeight 属性将返回元素的高度或宽度,而不会考虑溢出。
参考下面demo:
codepen: codepen.io/impressivew…
演示中的列具有相同的内容。左边列的 overflow 设置为 auto,右边列的 overflow 设置为 hidden。offsetHeight 属性为每个属性返回相同的值,因为它不考虑可滚动区域或隐藏区域;它只测量元素的实际高度,包括任何垂直填充和边框。
另一方面,名称恰当的 scrollHeight 属性将计算元素的全部高度,包括可滚动(或隐藏)区域:
codepen: codepen.io/impressivew…
上面的演示与前面的演示相同,只是它使用了 scrollHeightto 获取每个列的高度。再次注意,这两列的值是相同的。但这一次,它是一个更高的数字,因为溢出面积也被算作高度的一部分。
上面的示例主要关注元素高度,这是最常见的用例,但您也可以使用 offsetWidth 和 scrollWidth,它们将以相同的方式应用于水平滚动。
最后
以上就是我列出的DOM特性,这些可能是我在过去几年遇到的最有趣的特性,所以我希望在不久的将来,您至少能够在项目中使用其中的一个特性。