最近在写原生js,遇见了一些问题,然后通过查阅MDN文档,发现了很多知道但又不是很熟悉的api,所以就想着通读一下MDN,总结一些好用且优雅的api用法,来帮助我们快速开发。
下面先来看一些全局对象的继承关系。
全局对象的继承关系
Document
Element
dom元素
Window
document主要是对整个文档进行操作的。所以会定义一些创建元素的api。
element主要是对元素进行操作,所以会定义一些删除和添加元素的api。
我们发现这些全局对象都间接或直接继承EventTarget
,所以我们先来看看EventTarget
是个啥东西?
EventTarget
所有继承自他的目标对象都实现了事件监听addEventListener()
,事件移除removeEventListener
,事件派发dispatchEvent
三个方法。
前两种方法大家都很熟悉了。我们来介绍一下事件派发吧。
dispatchEvent
会向一个指定的事件目标派发一个 Event
,并以合适的顺序(同步地)调用所有受影响的 EventListener
。dispatchEvent()
会同步调用事件处理函数。在 dispatchEvent()
返回之前,所有监听该事件的事件处理程序将在代码继续前执行并返回。意思就是说我们在派发事件前,需要定义事件处理程序和事件。
Node
从上面全局对象继承关系来看,dom元素和document对象都继承自Node。他身上封装了很多和节点操作相关的api。
在DOM中,Node
是一个接口,由文档树中的不同类型的节点(包括元素、文本节点、注释等)实现。
baseURI
返回当前文档的url。即location.href
childNodes
他主要是获取当前节点元素直接子元素,并且获取的元素集合是实时更新的。如果你单纯想要获取直接子元素,他的效果比通过document.querySelectorAll()
获取好,因为通过document.querySelectorAll()
获取的元素不会实时更新。比如无限加载时,获取指定元素,childNodes
就可以获取实时加载的元素。
注意他们返回的都是类数组的NodeList,该对象提供了一些方法供我们使用
-
entries() 返回迭代器。遍历Nodelist键值对。
-
forEach() 遍历NodeList。
-
item() 根据给定的索引,返回一个
NodeList
对象中包含的 Node 对象。索引从0开始。等价于通过下标获取,只是越界返回值不同。前者返回null,后者返回undefined。 -
keys() 遍历NodeList key。即索引。
-
values 该方法返回一个 iterator 迭代器,可以利用迭代器遍历所有 value。即节点对象。
firstChild, lastChild, nextSibling, previousSibling
他们分别可以获取当前节点的第一个孩子节点,最后一个孩子节点,下一个兄弟节点, 上一个兄弟节点。最主要的是他们可以获取任何类型的节点(例如注释,文本节点)。
<body><!-- 22 -->
<div id="div-1">Here is div-1</div>
<div id="div-2">Here is div-2</div>
<script>
let el = document.getElementById("div-1");
console.log("nextSibling", el.nextSibling) // text
console.log("nextSibling", el.previousSibling, el.previousSibling.nextSibling === el) // text true
console.log(document.body.firstChild) // comment
nodeName, nodeType, nodeValue
他们分别返回当前节点名称(全大写),节点类型,节点值。
类型名称如下(正常节点的名称是全大写字母)
节点类型,返回数字,用于区分不同类型节点,类型映射如下。我们还可以访问提供的全局常量(例如Node.ELEMENT_NODE
)
节点值,如果nodeValue有值,那么我们可以设置其nodeValue。
ownerDocument
返回当前节点的顶层的 document 对象。这个主要看在iframe中返回的是根文档对象还是当前文档对象。经过测试发现,iframe中使用该属性返回的是当前文档对象,而非引用文档对象。
parentElement, parentNode
parentElement
返回当前节点的父元素节点,如果该元素没有父节点,或者父节点不是一个 DOM 元素,则返回 null
。
parentNode
返回指定的节点在 DOM 树中的父节点。他与parentElement
的区别就是它可以返回非Element元素。
textContent
这个api也是最近在写原生时学习到的,返回当前节点中的文本内容。
innerText 与 textContent区别
- innerText 只会返回可见元素的文本。并且不会获取script和style中的内容。
- textContent 会返回所有。
目前介绍一下比较根级的接口对象EventTarget, Node
,下篇文章会介绍Document, Element, Window
接口对象,欢迎大家关注。
appendChild(), cloneNode()
方法将一个节点附加到指定父节点的子节点列表的末尾处。如果将被插入的节点已经存在于当前文档的文档树中,那么 appendChild()
只会将它从原先的位置移动到新的位置(不需要事先移除要移动的节点)因为一个相同的节点不可能同时出现在文档的不同位置。如果想要不删除节点,那么我们可以使用cloneNode()
克隆一份该节点,然后在进行操作。
appendChild
返回追加后的节点。cloneNode(?deep 是否深度克隆)
返回调用该方法的节点的一个副本。- 克隆一个元素节点会拷贝它所有的属性以及属性值,当然也就包括了属性上绑定的事件 (比如
onclick="alert(1)"
),但不会拷贝那些使用addEventListener()
方法或者node.onclick = fn
这种用 JavaScript 动态绑定的事件。 - 如果
deep
参数设为false
,则不克隆它的任何子节点。该节点所包含的所有文本也不会被克隆,因为文本本身也是一个或多个的Text
节点。
- 克隆一个元素节点会拷贝它所有的属性以及属性值,当然也就包括了属性上绑定的事件 (比如
<div>
div标签
</div>
<foot></foot>
<script>
// 将dom树中的标签移动到其他位置
const div = document.querySelector("div")
const foot = document.querySelector("foot")
const r1= foot.appendChild(div)
console.log(r1 === div) // true
// 创建新节点
const p = document.createElement("p")
p.innerHTML = "p标签"
const r2 = document.body.appendChild(p)
console.log(r2 === p) // true
console.log("克隆", div.cloneNode(false)) // 空div标签,没有内容文本节点,所以一般都传递true,深度克隆。
</script>
compareDocumentPosition()
可以比较当前节点与任意文档中的另一个节点的位置关系。这个方法大致可以用来比较两个元素在文档中的位置关系,在开发中我们还是可以用来确认位置关系,然后做一些特定的操作。
contains()
判断给定节点是否是当前节点的后代,即该节点本身、其直接子节点(childNodes
)、子节点的直接子节点等。
这个方法可以用来实现popup类组件的点击mask区域隐藏功能实现,具体可以看这里
<div>
<p>
p元素
</p>
</div>
<script>
const p = document.querySelector('p')
document.body.addEventListener('click', (event) => {
console.log("点击的元素", event.target, p.contains(event.target))
})
</script>
getRootNode()
返回上下文中的根节点,如果 shadow DOM 可用,则对 shadow DOM 同样适用。
- 在标准的网页中调用将会返回一个
HTMLDocument
对象表示整个网页。 - 在 Shadow DOM 里调用将会返回一个与之相关联的
ShadowRoot
。
并且他可以传递一个options参数。options.composed = true
属性表示在检测shadow Dom时,是否跳过shadowRoot,直接返回顶层文档对象。
测试发现,如果嵌套iframe,那么他返回的依旧是内部文档对象,并不会跨文档返回根节点。
<div class="js-parent">
<div class="js-child"></div>
</div>
<div class="js-shadowHost"></div>
<script>
// work on Chrome 54+,Opera41+
var parent = document.querySelector(".js-parent"),
child = document.querySelector(".js-child"),
shadowHost = document.querySelector(".js-shadowHost");
console.log(parent.getRootNode()); // #document
console.log(child.getRootNode()); // #document
// create a ShadowRoot
var shadowRoot = shadowHost.attachShadow({ mode: "open" });
shadowRoot.innerHTML =
"<style>div{background:#2bb8aa;}</style>" +
'<div class="js-shadowChild">content</div>';
var shadowChild = shadowRoot.querySelector(".js-shadowChild");
// The default value of composed is false
console.log(shadowChild.getRootNode() === shadowRoot); // true
console.log(shadowChild.getRootNode({ composed: false }) === shadowRoot); // true
console.log(shadowChild.getRootNode({ composed: true }) === parent.getRootNode()); // true 跳过shadowRoot检测
</script>
hasChildNodes()
表示当前节点内部是否有子节点(文本,注释都算)
目前有三种方式可以判断是否有子节点了
- node.firstChild !== null
- node.childNodes.length > 0
- node.hasChildNodes()
参考
往期年度总结
往期文章
- ts装饰器的那点东西
- 这是你所知道的ts类型断言和类型守卫吗?
- TypeScript官网内容解读
- 经常使用ts的你,知道这些内容?
- 你有了解过原生css的scope?
- 现在比较常用的移动端调试你知道哪些?
- 众多跨标签页通信方式,你知道哪些?(二)
- 众多跨标签页通信方式,你知道哪些?
- 反调试吗?如何监听devtools的打开与关闭
- 因为原生,选择一家公司(前端如何防笔试作弊)
- 结合开发,带你熟悉package.json与tsconfig.json配置
- 如何优雅的在项目中使用echarts
- 如何优雅的做项目国际化
- 近三个月的排错,原来的憧憬消失喽
- 带你从0开始了解vue3核心(运行时)
- 带你从0开始了解vue3核心(computed, watch)
- 带你从0开始了解vue3核心(响应式)
- 3w+字的后台管理通用功能解决方案送给你
- 入职之前,狂补技术,4w字的前端技术解决方案送给你(vue3 + vite )