看到一篇有趣的文章你或许从未听说过的15个HTML元素方法 写的是 html 元素一些比较少见但是又简单有效的节点控制方法,还比较有意思。
因为我没有收藏翻看的习惯,所以摘录(稍做修改)下来。
初学者指导
首先让我们来讨论一下 HTML 与 DOM 之间的区别。
显然,普通的 <table> 元素就是一段 HTML 代码,它可以应用在任何一个以 .html 为扩展名的文件中。元素自带一系列特性(attribute),以控制它的显示方式与行为。
这就是元素的全部内容,它与 JavaScript 没有任何关联。
而 DOM 的作用是将你的 JavaScript 代码与文档中的 HTML 元素关联在一起,让你能够以对象的方式与这些元素进行交互。
这就是所谓的文档对象模型。
在 HTML 中的每个元素都对应着一个 DOM ‘接口’,其中定义了若干属性(property,通常会映射至 HTML 元素上的特性)与方法。举例来说,一个 <table> 元素对应着一个 HTMLTableElement 接口。
你可以按以下方式获取某个元素的引用:
const searchBox = document.getElementById('search-box');
现在,你就可以访问该元素上定义的所有属性与方法了。打个比方,你可以通过 searchBox.value
访问它的 value 属性,也可以调用 searchBox.focus()
方法让光标移至输入框上。
感谢你参加这个58秒的 DOM 入门培训课程,哈哈。
现在的问题在于,大多数元素都没有提供什么有趣的方法。因此,除非你特意到官方文档规范上去搜索那些可能永远都用不到的东西,否则很容易忽略掉那些零散的小技巧。
幸运的是,浏览规范与整理小技巧正是我用于避免陷入困境的两种最喜欢的方式。那么,让我们开始吧……
如果你也希望尝试一下这些技巧,又恰好有一些浏览器 DevTools 可以使用的话,可以在元素树型结构中先选中某个元素,然后在控制台中输入 $0
,它会返回给你一个所选中元素的引用。如果你需要返回该元素的对象,请输入 dir($0)
。
# querySelector() 与 querySelectorAll()
querySelector() 与 querySelectorAll() 的区别是一个是获取第一个筛选条件元素,一个获取全都的筛选条件元素
document.querySelector('.sub') // 获取body下的第一个 .sub
dom.querySelector('.sub') // 获取dom下的第一个 .sub
document.querySelectorAll('.sub') // 获取body下的全部 .sub
dom.querySelectorAll('.sub') // 获取dom下的全部 .sub
这两个方法都可以用更具体的筛选条件,这里一 querySelectorAll 为例:
// 直接获取
.querySelectorAll("div.aa")
// 类似属性选择器的写法
.querySelectorAll("div[class='aa']")
// 补充一下还可以not选择器
.querySelectorAll(".aa:not(ul)")
querySelectorAll 的筛选条件灵活,是不是跟jQuery好像。
querySelectorAll 跟 getElementsByClassName 获取到的元素的区别:
querySelectorAll 获取到的是数组
getElementsByClassName 获取到的是类数组,不能直接用forEach变量
例如
document.querySelectorAll('.sub').forEach(el => console.log("el:", el))
Array.from(document.getElementsByClassName('sub')).forEach(el => console.log("el2:", el))
绑定事件
document.getElementById('.idName').addEventListener('click', () => {})
document.querySelector('.className').addEventListener('click', () => {})
document.getElementsByClassName('className')[0].addEventListener('click', () => {})
# NodeList 的 forEach() 方法
某些情况下,当你获取到一个元素列表的引用时,可以通过 forEach()
方法进行迭代式调用。
假设你需要记录页面中所有链接的 URL,可以输入以下代码,只要你不介意看到报错。
document.getElementsByTagName('a').forEach(el => {}) // error
document.querySelectorAll('a').forEach(el => {});
问题出在 getElementsByTagName
与其他类似的 get… 方法返回的是一个 HTMLCollection
接口,而 querySelectorAll
返回的是一个 NodeList
接口。
关于什么是 NodeList ,来自 Mdn的解析:
Although
NodeList
is not anArray
, it is possible to iterate over it withforEach()
. It can also be converted to a realArray
usingArray.from()
.
大意是:
虽然 NodeList
返回的不是真正的数组,但是 NodeList
接口为我们提供了 forEach()
方法(此外还包括 keys()
、values()
,和 entries()
等方法 ),并且它可以用 Array.from()
转化为真正的数组。
所以,这样的代码就能够正常工作:
Array.from(document.getElementsByTagName('a')).forEach(el ==> {
console.log(el.href);
});
Array.from(document.querySelectorAll('a'))
.map(el => el.origin)
.filter(origin => origin !== document.origin)
# closest
该方法可在任意元素上使用,它能够向上查找元素的树型结构,可以理解为 与 querySelector()
相反的方法。因此,我可以通过以下方法获取当前内容的对应标头:
myElement.closest('article').querySelector('h1');
这段方法首先向上找到最近的 <article>
元素,然后再向下找到最近的 <h1>
元素。
# getBoundingClientRect()
Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置,单位为 px。
document.querySelector('h3').getBoundingClientRect()
返回
{
bottom: 165
height: 100
left: 48
right: 863
top: 65
width: 815
x: 48
y: 65
}
image.png
不过,在调用该方法时需要注意两点:
- 调用该方法会导致元素的重绘,根据设备与页面复杂程度的不同,重绘的时间可能会占用几毫秒。因此,如果你需要重复地调用该方法,例如在使用动画的场景下,需要特别注意这一点。
- 并非所有的浏览器都会返回这些值,他们有这个责任么?
# getcomputedstyle()和style
window.getComputedStyle方法是只读的,不能设置,返回包含指定DOM元素的所有css属性;
element.style能读能写,只能获取通过内联样式设置的值
var style = window.getComputedStyle(element [, pseudoElt]);
作用:
getComputedStyle方法执行会返回一个对象,该对象在包含指定DOM元素(或伪类)计算样式之后(实际显示)的所有CSS属性的值,并且会随着元素的样式更改自动更新,通过使用CSS属性名称进行索引可以获取该CSS属性值。
参数:
第一个参数指定一个用来获取计算样式的DOM元素,
第二个参数(可选)指定一个要匹配的伪元素的字符串,普通元素可省略或传null
兼容性:
getComputedStyle方法IE6~8是不支持的
用法:
获取普通属性
window.getComputedStyle(this.$refs.element, null).height // 这里第二个值可省略
// 或者
this.$refs.element.style.height
getComputedStyle 可以从伪元素拉取样式信息 (比如, ::after
, ::before
, ::marker
, ::line-marker
)
<style>
h3::after {
content: "rocks!";
}
</style>
<h3>generated content</h3>
<script>
let h3 = document.querySelector('h3')
getComputedStyle(h3, '::after').content //rocks!
</script>
# matches()
假设我需要检查某个元素是否包括一个特定的 class。
这是最复杂的方式:
if (myElement.className.indexOf('some-class') > -1) {
// do something
}
// 或者
if (myElement.className.includes('some-class')) {
// do something
}
最佳方式:
if (myElement.matches('.some-class')) {
// do something
}
# insertAdjacentElement()
我今天才刚学到这一条!它的作用类似于 appendChild()
,但能够更好地控制插入子元素的具体位置。
parentEl.insertAdjacentElement('beforeend', newEl)
与 parentEl.appendChild(newEl)
的作用是一样的,但除此之外,你还可以指定 beforebegin
、afterbegin
或 afterend
这几个参数值,元素将按这些值的名称所示插入相应的位置。
多么强大的控制能力!
多棒的点子。
# contains()
你有没有遇到过这样的情形,需要知道某个元素是否被包含在另一个元素中?至少我本人经常会遇到这样的问题。
打个比方,假设我在处理一个鼠标点击事件时,需要知道它是发生在一个模态窗口中还是发生在外面(这样我才能够关闭这个窗口),我大概会这么做:
const handleClick = e => {
if (!modalEl.contains(e.target)) modalEl.hidden = true;
};
代码中的 modalEl
是模态窗口的引用,而 e.target
则代表各种发生点击事件的元素。
有趣的是,每当遇到这种情形,在我第一遍写代码的时候,100%的概率会将其中的判断逻辑写反。
哪怕是我提高了警惕,并在保存代码之前尝试将逻辑颠倒过来写,仍然还是写错。
# getAttribute()
这毫无疑问是所有元素方法中最没用的一个,但有一个场景除外。
你是否记得,我在本文的开头部分曾提到,对象的属性 property 通常也会映射到它的特性 attribute 中(我在上文中特别用粗体强调了这一点,注意不是斜体)?
但在某一个场景中,这种假设并不成立,这就是某个元素的 href
特性,例如 <a href="/animals/cat">Cat</a>
。
调用 el.href
不会返回 /animals/cat
,这可能与你的猜测不符。原因在于 <a>
元素实现了 HTMLHyperlinkElementUtils 接口,该接口提供了一系列辅助属性,例如 prototol
与 hash
等等,以展现与链接的目标相关的值。
href
就是其中一个实用的属性,它将返回完整的 URL,并去掉无用的空格,而不是返回在特性中所指定的相对 URL。
这样一来,如果你需要获取 href
特性中的字符串字面值,就只能使用 el.getAttribute('href')
方法了。
# dialog 元素的三大法宝
<dialog>
是一个相对较新的元素,它带来了两个还算能用的方法,和一个非常棒的方法。其中show()
和 close()
方法的功能与你所想象的一样,我感觉还算可以。
而 showModal()
方法能够将 <dialog>
元素显示在页面的顶层,居中对齐,这正是所期望的模态窗口行为。你无需指定 z-index
,或者手动添加一个灰色的背景,也不需要监听 esc 按键以关闭此窗口。浏览器能够理解模态窗口的工作方式,并自动完成你所期望的行为。
这真是太棒了。
# 表单
或许你已经知道,<form>
有一个 submit()
方法。但或许你不知道表单还有一个 reset()
方法,而且当你需要对表单元素进行验证时,还可以调用 reportValidity()
方法。
此外,你也可以通过对表单的 elements
属性加上元素的 name
特性 的方式调用它的属性。打个比方,myFormEl.elements.email
将返回属于某个 <form>
中的 <input name="email" />
元素(‘属于’,并不代表它一定是一个‘子元素’)。
好吧,其实刚才我是骗你的。elements
并不会返回一个元素列表,而是返回一个控件列表(显然它不是一个数组,因为没必要这么做)。
举例来说:假设你有三个单选按钮,每个都有相同的名称 animal
,那么 formEl.elements.animal
将返回一个单选按钮集的引用(一个控件,三个元素)。
而 formEl.elements.animal.value
将返回所选中的单选按钮的值。
这种语法看起来非常古怪,让我们来分解一下看看:formEl
是一个元素,elements
则对应 HTMLFormControlsCollection 接口,这并非一个真正的数组,其中的每一项内容也未必代表一个 HTML 元素。animal
是多个单选按钮的集合,只是因为他们具有相同的 name
特性才聚集在一起(RadioNodeList 接口就是为此而生的),而 value
则返回该集合中所选中的那个单选按钮的 value
特性。
非常直观,嗯……
# table 的方法
原始的 table 元素(时至今日仍然是网站布局方法里的第一名)本身自带许多精巧的方法,使用这些方法创建表格就像搭建宜家里的桌子一样简单。
以下是部分实用的方法。
const tableEl = document.querySelector('table');
const headRow = tableEl.createHead().insertRow();
headerRow.insertCell().textContent = 'Make';
headerRow.insertCell().textContent = 'Model';
headerRow.insertCell().textContent = 'Color';
const newRow = tableEl.insertRow();
newRow.insertCell().textContent = 'Yes';
newRow.insertCell().textContent = 'No';
newRow.insertCell().textContent = 'Thank you';
整段代码里完全用不着使用 document.createElement()
方法。
如果你在一个 table 元素上直接调用 .insertRow()
方法,它甚至会自动为你插入一个 <tbody>
元素,是不是很棒?
# select()
我或许应该重新组织一下本文,让最后一条内容不会显得过于无足轻重。不管怎样,.select()
方法会将你指定的元素中的所有内容全选。
真方便。
# scrollIntoView()
你知道吗?当页面的 URL 中包含 #something
元素时,一旦页面加载,浏览器就会自动滚动至具有这个 ID 的元素之处。
这确实是一项很贴心的功能,但如果你在页面加载之后再渲染元素,这项功能就不起作用了。
不过,你也可以通过以下方式,手动地让这项功能重新生效:
document.querySelector(document.location.hash).scrollIntoView();
# hidden
好吧,hidden 或许不是一个方法,但如果你提出抗议,那我也要争论一下:在 hidden 的背后很可能对应着一个 setter,这可是一个货真价实的方法,对不对?
不管怎样,你是否曾经为了隐藏某个元素而使用过 myElement.style.display = 'none'
这种方法呢?如果是的话,请别再这么做了!
只需调用 myElement.hidden = true
,即可实现元素隐藏的功能。
# toggle()
嗯,toggle 也不算是元素的方法,它实际上是元素属性上的一个方法。严格来说,这是一种为元素添加或删除某个 class 的方法,具体做法是 myElement.classList.toggle('some-class')
。
如果你曾经通过 if 条件语句为元素添加 class,那就应该赶紧改用这种做法。
正确的方式是为 toggle
方法传入第二个参数,如果该参数返回 true ,则指定的 class 就会添加至元素上。
el.classList.toggle('some-orange-class', theme === 'orange');
我知道你在想些什么:这种写法违背了 ‘toggle’ 这个词的本义(开关),那些从 IE 时代过来的开发者们都这么想,他们断言应当彻底摒弃使用第二个参数的做法。
所以,我收回我的话。不必坚持这种写法了,各位请随意!
网友评论