本文是学习总结 现代 JavaScript 教程 的学习笔记
遍历 DOM⭐
获取节点
- 所有子节点:
ele.childNodes
,返回值是一个可迭代对象,不是数组 - 第一个子节点:
ele.firstChild
- 最后一个子节点:
ele.lastChild
- 是否有子节点:
ele.hasChildNodes
- 下一个兄弟节点:
ele.nextSibling
- 上一个兄弟节点:
ele.previousSibling
- 父节点:
ele.parentNode
注意:节点是包括注释、文本(document等)等等都涵盖在内的。
如果我们只是单纯的关心操纵的是代表标签的和形成页面结构的元素节点,那么使用下面的方法:
获取纯元素节点
这些链接和我们在上面提到过的类似,只是在词中间加了 Element
:
children
— 仅那些作为元素节点的子代的节点。firstElementChild
,lastElementChild
— 第一个和最后一个子元素。previousElementSibling
,nextElementSibling
— 兄弟元素。parentElement
— 父元素。
表格
表格的
<table>
标签的下一层一定是<tbody>
,即使你的 html 里没写,dom 也会在加载的时候加上去,所以table.firstElementChild == <tbody>
<table>
元素支持 (除了上面给出的,之外) 以下这些属性:
table.rows
—<tr>
元素的集合。table.caption/tHead/tFoot
— 引用元素<caption>
,<thead>
,<tfoot>
。table.tBodies
—<tbody>
元素的集合(根据标准还有很多元素,但是这里至少会有一个 — 即使没有被写在 HTML 源文件中,浏览器也会将其放入 DOM 中)。
<thead>
,<tfoot>
,<tbody>
元素提供了 rows
属性:
tbody.rows
— 表格内部<tr>
元素的集合。
<tr>
:
tr.cells
— 在给定<tr>
中的<td>
和<th>
单元格的集合。tr.sectionRowIndex
— 给定的<tr>
在封闭的<thead>/<tbody>/<tfoot>
中的位置(索引)。tr.rowIndex
— 在整个表格中<tr>
的编号(包括表格的所有行)。
<td>
和 <th>
:
td.cellIndex
— 在封闭的<tr>
中单元格的编号。
查询搜索节点⭐
用法
- ele.querySelectorAll(css) :返回一个满足 css 选择器的所有节点的可迭代对象
- ele.querySelector(css) :返回满足 css 选择器的第一个元素节点
- ele.matches(css) :检查 ele 是否与给定的 css 选择器匹配,返回 bool
- ele.closest(css) :搜索距离 ele 最近的 (包括 ele 本身) 父级链上的第一个匹配 css 选择器的元素
补充:css 选择器
选择器 | 例子 | 例子描述 | CSS |
---|---|---|---|
.class | .intro | 选择 class="intro" 的所有元素。 | 1 |
#id | #firstname | 选择 id="firstname" 的所有元素。 | 1 |
* | * | 选择所有元素。 | 2 |
element | p | 选择所有 元素。 | 1 |
element,element | div,p | 选择所有 元素和所有 元素。 | 1 |
element element | div p | 选择 元素内部的所有 元素。 | 1 |
element>element | div>p | 选择父元素为 元素的所有 元素。 | 2 |
element+element | div+p | 选择紧接在 元素之后的所有 元素。 | 2 |
[attribute] | [target] | 选择带有 target 属性所有元素。 | 2 |
[attribute=value] | [target=_blank] | 选择 target="_blank" 的所有元素。 | 2 |
[attribute~=value] | [title~=flower] | 选择 title 属性包含单词 "flower" 的所有元素。 | 2 |
[attribute|=value] | [lang|=en] | 选择 lang 属性值以 "en" 开头的所有元素。 | 2 |
:link | a:link | 选择所有未被访问的链接。 | 1 |
:visited | a:visited | 选择所有已被访问的链接。 | 1 |
:active | a:active | 选择活动链接。 | 1 |
:hover | a:hover | 选择鼠标指针位于其上的链接。 | 1 |
:focus | input:focus | 选择获得焦点的 input 元素。 | 2 |
:first-letter | p:first-letter | 选择每个 元素的首字母。 | 1 |
:first-line | p:first-line | 选择每个 元素的首行。 | 1 |
:first-child | p:first-child | 选择属于父元素的第一个子元素的每个 元素。 | 2 |
:before | p:before | 在每个 元素的内容之前插入内容。 | 2 |
:after | p:after | 在每个 元素的内容之后插入内容。 | 2 |
:lang(language) | p:lang(it) | 选择带有以 "it" 开头的 lang 属性值的每个 元素。 | 2 |
element1~element2 | p~ul | 选择前面有 元素的每个
| 3 |
[attribute^=value] | a[src^="https"] | 选择其 src 属性值以 "https" 开头的每个 元素。 | 3 |
[attribute$=value] | a[src$=".pdf"] | 选择其 src 属性以 ".pdf" 结尾的所有 元素。 | 3 |
[attribute*=value] | a[src*="abc"] | 选择其 src 属性中包含 "abc" 子串的每个 元素。 | 3 |
:first-of-type | p:first-of-type | 选择属于其父元素的首个 元素的每个 元素。 | 3 |
:last-of-type | p:last-of-type | 选择属于其父元素的最后 元素的每个 元素。 | 3 |
:only-of-type | p:only-of-type | 选择属于其父元素唯一的 元素的每个 元素。 | 3 |
:only-child | p:only-child | 选择属于其父元素的唯一子元素的每个 元素。 | 3 |
:nth-child(n) | p:nth-child(2) | 选择属于其父元素的第二个子元素的每个 元素。 | 3 |
:nth-last-child(n) | p:nth-last-child(2) | 同上,从最后一个子元素开始计数。 | 3 |
:nth-of-type(n) | p:nth-of-type(2) | 选择属于其父元素第二个 元素的每个 元素。 | 3 |
:nth-last-of-type(n) | p:nth-last-of-type(2) | 同上,但是从最后一个子元素开始计数。 | 3 |
:last-child | p:last-child | 选择属于其父元素最后一个子元素每个 元素。 | 3 |
:root | :root | 选择文档的根元素。 | 3 |
:empty | p:empty | 选择没有子元素的每个 元素(包括文本节点)。 | 3 |
:target | #news:target | 选择当前活动的 #news 元素。 | 3 |
:enabled | input:enabled | 选择每个启用的 元素。 | 3 |
:disabled | input:disabled | 选择每个禁用的 元素 | 3 |
:checked | input:checked | 选择每个被选中的 元素。 | 3 |
:not(selector) | :not(p) | 选择非 元素的每个元素。 | 3 |
::selection | ::selection | 选择被用户选取的元素部分。 | 3 |
节点属性
nodeType
在一开始我们知道有元素节点和文本节点等不同的节点,我们可以通过 nodeType
属性来区分或用作筛选
- 元素节点:ele.nodeType == 1
- 文本节点:ele.nodeType == 3
- document:ele.nodeType == 9
- 其他值参见 规范
标签名
ele.nodeName
:用于任意节点ele.tagName
:仅用于元素节点。文本节点等会返回undefined
注意:元素节点的名字始终是大写,比如: BODY DIV
,而其他如文本元素返回值是: #comment #document
获取与修改节点值⭐
innerHTML 和 outerHTML
取值
innerHTML
:获取节点元素标签内部 htmlouterHTML
:获取节点元素完整 html
<html>
<body>
<div><p>Hello, world!</p></div>
<script>
let div = document.querySelector('div');
console.log(div.innerHTML) // <p>Hello, world!</p>
console.log(div.outerHTML) // <div><p>Hello, world!</p></div>
</script>
</body>
</html>
设值
ele.innerHTML = "xxx"
会替换ele
内本身的内容ele.innerHTML += "<p>fwf</p>"
会增加上p
元素,而ele.innerHTML.append("<p>fwf</p>")
只会添加一个 "<p>fwf</p>" 的文本节点在 HTML 里ele.outerHTML = "xxx"
会把原 dom(ele) 整个替换成新 dom(假设是 eleNew),但是ele.outerHTML
的值还是原来的值,因为他指向原 dom ,而不是我们新的eleNew
,举个例子:
<html>
<body>
<div><p>Hello, world!</p></div>
<script>
let pElem = document.querySelector('p');
console.log(pElem.outerHTML) // <p>Hello, world!</p>
pElem.outerHTML = "<span>i'm new element</span>" // 此时画面上变成了 i'm new element
console.log(pElem.outerHTML) // <p>Hello, world!</p> [这里 pEle 还是指向 p 元素,尽管 p 元素已经没有了,但是值还在]
console.log(document.querySelector('span').outerHTML) // <span>i'm new element</span>
</script>
</body>
</html>
textContent
上面的 innerHTML
假设执行 ele.innerHTML = "<p>text</p>"
,此时会写入一个 p
的元素节点进 HTML ,如果我们想把他作为一整个纯文本写入 ,就可以使用 textContent
。textContent
读取的时候是去掉所有 tags,仅保留文本
document.querySelector("div").textContent="<p>text</p>"
ele.innerText
也能做到写入和获取内容的事情,但是二者有所区别:
innerText
不会换行,textContent
会
<div id="container">
<span>666</span>
<span>999</span>
</div>
<script>
var oDiv=document.getElementById('container');
console.log(oDiv.innerText,oDiv.textContent);
</script>
输出结果:
innerText:666 999
textContent:
666
999
- 如果一个元素之间包含了
script
标签或者style
标签,innerText
是获取不到这两个元素之间的文本的,而textContent
可以
<div id="container">
<script>var a=666;</script>
<span>666</span>
<span>999</span>
</div>
<script>
var oDiv=document.getElementById('container');
console.log(oDiv.innerText,oDiv.textContent);
</script>
输出结果:
innerText:666 999
textContent:
var a=666;
666
999
textContent
可以作用与文本节点等
nodeValue/data
上面的 innerHTML
仅仅对节点元素有效,如果需要获取(注意!仅获取)注释或文本节点内容,可以使用 nodeValue/data
,这两个的效果并无差异:
<body>
Hello
<!-- Comment -->
<script>
let text = document.body.firstChild;
alert(text.data); // Hello
let comment = text.nextSibling;
alert(comment.data); // Comment [注释节点的data是 <!-- --> 这个标签里面的值!]
</script>
</body>
三者区别⭐
innerHTML/outerHTML
仅作用于标签节点,设置带有 tag 的字符串会转换成 HTMLtextContent
取值会去掉 tag ,写入就是纯文本,不转换 HTML ,能作用于任何节点data/nodeValue
仅作用于文本节点等
其他属性
hidden
— 隐藏元素,相当于style = "display:none"
,使用方式:
<div hidden>With the attribute "hidden"</div>
ele.hidden = true
value
—<input>
,<select>
和<textarea>
(HTMLInputElement
,HTMLSelectElement
……)的 value。href
—<a href="...">
(HTMLAnchorElement
)的 href。id
— 所有元素(HTMLElement
)的 “id” 特性(attribute)的值。
特性和属性(Attributes and properties)
概念
-
DOM 属性(properties)
- properties 是大部分是内建且订好的,除非我们在原型链上修改或增加
- 可以用
ele.prop
获取 properties - 可以用
ele.prop = 'xx'
修改 properties
-
HTML 特性(attributes)
- attributes 分为 标准 和 非标准 ,浏览器渲染 DOM 的时候, 会根据标准 attribute 创建出 DOM property ,但是非标准不会,例如:
<body id="test" something="non-standard">
<script>
alert(document.body.id); // test
// 非标准的特性没有获得对应的属性
alert(document.body.something); // undefined
</script>
</body>
- 操作 attribute 的方法如下:
elem.hasAttribute(name)
— 检查特性是否存在elem.getAttribute(name)
— 获取这个特性值elem.setAttribute(name, value)
— 设置这个特性值elem.removeAttribute(name)
— 移除这个特性
prop 和 attr 同步⭐
当一个标准的 attr 被修改,对应的 prop 也会被改变,反之亦然:
<input>
<script>
let input = document.querySelector('input');
// 特性 => 属性
input.setAttribute('id', 'id');
alert(input.id); // id(被更新了)
// 属性 => 特性
input.id = 'newId';
alert(input.getAttribute('id')); // newId(被更新了)
</script>
但是个别特例
比如:
<input>
<script>
let input = document.querySelector('input');
// 特性 => 属性
input.setAttribute('value', 'text');
alert(input.value); // text
// 这个操作无效,属性 => 特性
input.value = 'newValue';
alert(input.getAttribute('value')); // text(没有被更新!)
</script>
在上面这个例子中:
- 改变特性值 value 会更新属性。
- 但是属性的更改不会影响特性。
比如: .href
会返回完整 link ,而 getAttribute('href')
会返回 HTML 中内容
<a href="/tutorial">/tutorial.html</a>
document.querySeletor('a').href // => https://run.plnkr.co/tutorial
document.querySeletor('a').getAttribute('href') // => /tutorial
data-*
如果我们出于我们的目的使用了非标准的特性,之后它被引入到了标准中并有了其自己的用途,该怎么办?
为了避免冲突,存在 data-* 特性。所有以 “data-” 开头的特性均被保留供程序员使用。它们可在 dataset 属性中使用。
<html>
<body>
Hello
<div data-info-test="test"><p>Hello, world!</p></div>
<script>
console.log(document.querySelector("div").dataset.infoTest) // test
document.querySelector("div").dataset.infoTest = "new"
console.log(document.querySelector("div").dataset.infoTest) // new
</script>
</body>
</html>
使用的时候用
dataset.xxx
,且为驼峰写法
处理 DOM 元素
创建
document.createElement(tag)
--- 创建一个元素节点document.createTextNode(str)
--- 创建一个文本节点
插入
以下都可以插入多个节点或字符串,用 "," 分隔:
node.append(...nodes or strings)
—— 在node
末尾 插入节点或字符串,node.prepend(...nodes or strings)
—— 在node
开头 插入节点或字符串,node.before(...nodes or strings)
—— 在node
前面 插入节点或字符串,node.after(...nodes or strings)
—— 在node
后面 插入节点或字符串,node.replaceWith(...nodes or strings)
—— 将node
替换为给定的节点或字符串。
但是上面的方法,加入我们使用 node.append('<p>hello</p>')
,那么会把 <> </>
这些转义,作为一般“文本”插入
加入我们希望自动将这种转化为 HTML ,可以使用:ele.insertAdjacentHTML(where, html)
该方法的第一个参数是代码字(code word),指定相对于 elem 的插入位置。必须为以下之一:
- "beforebegin" — 将 html 插入到 elem 前插入,
- "afterbegin" — 将 html 插入到 elem 开头,
- "beforeend" — 将 html 插入到 elem 末尾,
- "afterend" — 将 html 插入到 elem 后。
第二个参数是 HTML 字符串,会作为 HTML 插入
这个方法有两个兄弟,但是用的很少:
elem.insertAdjacentText(where, text)
— 语法一样,但是将 text 字符串“作为文本”插入而不是作为 HTML,elem.insertAdjacentElement(where, elem)
— 语法一样,但是插入的是一个元素。
删除与移动
node.remove()
--- 删除节点- 我们在把一个已有的节点插入到另一个位置的时候,会自动删除原有的节点,也就是移动
<div id="first">First</div>
<div id="second">Second</div>
<script>
// 无需调用 remove
second.after(first); // 获取 #second,并在其后面插入 #first
</script>
克隆
elem.cloneNode(bool)
: bool
为 true
代表深克隆(克隆所有 attr 和子元素), bool
为 false
则不包括子元素。
总结⭐
- 创建新节点的方法:
- document.createElement(tag) — 用给定的标签创建一个元素节点,
- document.createTextNode(value) — 创建一个文本节点(很少使用),
- elem.cloneNode(deep) — 克隆元素,如果 deep==true 则与其后代一起克隆。
- 插入和移除节点的方法:
- node.append(...nodes or strings) — 在 node 末尾插入,
- node.prepend(...nodes or strings) — 在 node 开头插入,
- node.before(...nodes or strings) — 在 node 之前插入,
- node.after(...nodes or strings) — 在 node 之后插入,
- node.replaceWith(...nodes or strings) — 替换 node。
- node.remove() — 移除 node。
- 文本字符串被“作为文本”插入。
- 在html中给定一些 HTML,elem.insertAdjacentHTML(where, html)会根据where的值来插入它:
- "beforebegin" — 将 html 插入到 elem 前面,
- "afterbegin" — 将 html 插入到 elem 的开头,
- "beforeend" — 将 html 插入到 elem 的末尾,
- "afterend" — 将 html 插入到 elem 后面。
另外,还有类似的方法,elem.insertAdjacentText 和 elem.insertAdjacentElement,它们会插入文本字符串和元素,但很少使用。
题目
从对象创建树
编写一个函数 createTree,从嵌套对象创建一个嵌套的 ul/li 列表(list)。
<html>
<body>
<div id="container">
</div>
<script>
let data = {
"Fish": {
"trout": {},
"salmon": {}
},
"Tree": {
"Huge": {
"sequoia": {},
"oak": {}
},
"Flowering": {
"apple tree": {},
"magnolia": {}
}
}
};
let container = document.getElementById('container');
createTree(container, data); // 将树创建在 container 中
/**
* @description:
* @Author: rodrick
* @Date: 2020-12-05 19:56:40
* @param {*} elem
* @param {*} data
* @return {*}
*/
function createTree(elem, data) {
// 创建一个ul
let ulElem = document.createElement("ul")
// 遍历对象
Object.entries(data).forEach(([key, val]) => {
// 创建一个li,会塞进 ulElem
let liElem = document.createElement("li")
// 先把key塞进li的值
liElem.innerHTML = key
if (Object.keys(val).length != 0) {
// 如果这个value有值,值和这个li扔进递归,递归里li继续嵌套ul
ulElem.append(createTree(liElem, val))
} else {
ulElem.append(liElem)
}
})
// ul 塞入 elem
elem.append(ulElem)
return elem
}
</script>
</body>
</html>
最终结果:
样式 class/style
管理 class,有两个 DOM 属性:
className
— 字符串值,可以很好地管理整个类的集合。classList
— 具有:elem.classList.add/remove(class)
— 添加/移除类。elem.classList.toggle(class)
— 如果类不存在就添加类,存在就移除它。elem.classList.contains(class)
— 检查给定类,返回true/false
。
要改变样式:
style
属性是具有驼峰(camelCased)样式的对象。对其进行读取和修改与修改"style"
特性(attribute)中的各个属性具有相同的效果。
直接 ele.style.xxx = "aaa"
修改即可,注意要带上单位(px 等)
getComputedStyle(elem, [pseudo伪元素])
返回 elem 的所有样式。只读。
元素大小与滚动⭐
clientWidth/Height
—— width + paddingoffsetWidth/Height
—— width + padding + scrollbar + borderclientLeft/Top
—— scrollbar(在左/上的情况下) + borderoffetLeft/Top
—— border 以上/左 的距离scrollWidth/Height
—— 完整滚动文档区域(横向或竖向)的宽高(不是可视区域)scrollTop/Left
—— 完整滚动文档区域宽/高 - 可视区域宽/高,可以理解为已滚动了多少
scrollTop/Left
是 可修改 的,比如scrollTop += 10
,那么就会向下滚动 10px,这样就可以做出滚动效果。
窗口大小和滚动⭐
document.documentElement.clientWidth/clientHeight
不带滚动条的可视窗口宽高window.innerWidth/innerHeight
带有滚动条的可视窗口宽高window.outerWidth/outerHeight
整个浏览器的宽高
滚动
- 读取当前的滚动:
window.pageYOffset/pageXOffset
。 - 更改当前的滚动:
window.scrollTo(pageX,pageY)
— 绝对坐标,window.scrollBy(x,y)
— 相对当前位置进行滚动,elem.scrollIntoView(bool)
— 滚动以使elem
可见(elem
与窗口的顶部/底部对齐)。bool
为true(缺省值)
时,将elem
定位到顶部,bool
为false
时,将elem
滚动到底部
- 禁止滚动:
document.body.style.overflow = 'hidden'
坐标
获取一个元素的坐标: elem.getBoundingClientRect()
,他会返回一个对象,对象的各个 key 对应的 value 含义如下图,详细坐标说明参见 JS现代教程 :