使用 attributes 属性遍历元素特性

// 迭代元素的每一个特性,将它们构造成 name = value 的字符串形式
function outputAttributes (element) {
const pairs = []
let attrName
let attrValue for (let i = 0, len = element.attributes.length; i < len; i++) {
attrName = element.attributes[i].nodeName
attrValue = element.attributes[i].nodeValue
pairs.push(`${attrName}=${attrValue}`)
}
return pairs.join(" ")
}

使用 classList 属性来操作类名

<div class="bd user disabled">...</div>

这个 <div> 元素一共有三个类名。要从中删除一个类名,需要把这三个类名拆开,删除不想要的那个,然后再把其他类名拼成一个新字符串。请看下面的例子:

// div.className = 'bd user disabled'
let classNames = div.className.split(/\s+/)
let pos = -1
for (let i = 0, len = classNames.length; i < len; i++) {
if (classNames[i] === 'user') {
pos = i
break
}
} classNames.splice(pos, 1) // 删除类名
div.className = classNames.join(' ') // 把剩下的类名拼接成字符串并重新设置

HTML5 新增了一种操作类名的方式,可以让操作更简单也更安全,那就是为所有元素添加 classList 属性,这个新类型定义了如下方法:

  • add(value):将给定的字符串值添加到列表中。如果值已经存在,就不添加了。
  • contains(value):表示列表中是否存在给定的值,如果存在则返回 true,否则返回 false。
  • remove(value):从列表中删除给定的字符串。
  • toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。

这样,前面那么多行代码用下面这一行代码就可以代替了

div.classList.remove("user")

使用 Element Traversal API 操作元素节点

过去,要遍历某元素的所有子元素,需要像下面这样写代码:

let child = element.firstChild
while (child !== element.lastChild) {
if (child.nodeType === 1) { // 检查是否为元素节点
processChild(child)
}
child = child.nextSibling
}

Element Traversal API 为 DOM 元素添加了以下 5 个属性:

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

使用 Element Traversal 新增的 API,代码会更简洁:

let child = element.firstElementChild
while (child !== element.lastElementChild) {
processChild(child) // 肯定是元素节点
child = child.nextElementSibling // 遍历下一个元素节点
}

getElementsByTagName('*') 会返回什么?

最近看到一道面试题:找出页面出现最多的标签,或者说出现次数最多的前 2、3 个标签,可以试着自己实现下。

// 获取元素列表,以键值对的形式存储为一个对象
function getElements () {
// 如果把特殊字符串 "*" 传递给 getElementsByTagName() 方法
// 它将返回文档中所有元素的列表,元素排列的顺序就是它们在文档中的顺序。
// 返回一个 HTMLCollection - 类数组对象
const nodes = document.getElementsByTagName('*')
const tagsMap = {}
for (let i = 0, len = nodes.length; i < len; i++) {
let tagName = nodes[i].tagName
if (!tagsMap[tagName]) {
tagsMap[tagName] = 1
} else {
tagsMap[tagName] ++
}
}
return tagsMap
} // n 为要选取的标签个数 - 即出现次数前 n 的标签名
// 将上面的方法获取的对象的键值对取出组成数组,按出现次数排序
function sortElements (obj, n) {
const arr = []
const res = []
for (let key of Object.keys(obj)) {
arr.push({ tagName: key, count: obj[key] })
} // 冒泡
for (let i = arr.length - 1; i > 0; i--) {
for (let j = 0; j < i; j++) {
if (arr[j].count < arr[j + 1].count) { // 升序
swap(arr, j, j + 1)
}
}
} for (let i = 0; i < n; i++) {
res.push(arr[i].tagName)
}
return res
} function swap (arr, index1, index2) {
let temp = arr[index1]
arr[index1] = arr[index2]
arr[index2] = temp
} let res = sortElements(getElements(), 2)
console.log(res)

动态添加脚本与样式

// 动态添加脚本
function loadScript (url) {
var script = document.createElement("script")
script.type = "text/javascript"
script.src = url
document.body.appendChild(script)
} // 动态添加样式
function loadStyles (url) {
var link = document.createElement("link")
link.rel = "stylesheet"
link.type = "text/css"
link.href = url
var head = document.getElementsByTagName("head")[0]
head.appendChild(link)
}

使用 contains() 方法判断某个节点是否为另一个节点的后代

contains() 方法用于判断某个节点是否为另一个节点的后代,调用 contains() 方法的应该是祖先节点,也就是搜索开始的节点,这个方法接收一个参数,即需要检测的节点。

console.log(document.documentElement.contains(document.body)) // true

这个例子检测了 <body> 元素是不是 <html> 元素的后代

Element.getBoundingClientRect() 及 dataset 的使用

这是 小册 上的一个例子,使用原生 JS 实现图片懒加载,需要了解这两个知识点

1.Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置。具体解释及用法参考 MDN

通过 Element.getBoundingClientRect().top

window.innerHeight(当前视窗的高度)比较就可以判断图片是否出现在可视区域。

注意这个 top 是相对于当前视窗的顶部的 top 值而不是一开始的顶部。

2.通过 Element.dataset 可以获取到为元素节点添加的 data-* 属性,我们可以通过这个属性来保存图片加载时的 url。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
}
.box-image {
display: flex;
flex-direction: column;
align-items: center;
}
img {
display: inline-block;
height: 300px;
margin-bottom: 20px;
}
</style>
</head> <body>
<div class="box-image">
<img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/6.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/8.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/aotu-10.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/aotu-15.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/05/%E7%BD%AA%E6%81%B6%E7%8E%8B%E5%86%A04.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/05/%E7%BD%AA%E6%81%B6%E7%8E%8B%E5%86%A06.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/9.jpg" alt="">
<img src="" class="image-item" lazyload="true" data-original="http://cmk1018.cn/wp-content/uploads/2019/04/aotu-16.jpg" alt="">
</div>
<script>
var viewHeight = document.documentElement.clientHeight;
// 节流:加一个 300ms 的间隔执行
function throttle(fn, wait) {
let canRun = true
return function (...args) {
if (!canRun) return
canRun = false
setTimeout(() => {
fn.apply(this, args)
canRun = true
}, wait)
}
}
function lazyload() {
let imgs = document.querySelectorAll('img[data-original][lazyload]') // 获取文档中所有拥有 data-original lazyload 属性的<img>节点
imgs.forEach(item => {
if (item.dataset.original == '') {// HTMLElement.dataset 访问在 DOM 中的元素上设置的所有自定义数据属性(data-*)集。
return
}
// 返回一个 DOMRect 对象,包含了一组用于描述边框的只读属性——left、top、right 和 bottom,
// 单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。
let rect = item.getBoundingClientRect()
// 其 top 值是相对于当前视窗的顶部而言的而不是绝对的顶部,所以 top 值 < window.innerHeight 的话图片就出现在底部了就需要加载
if (rect.bottom >= 0 && rect.top < viewHeight) {
let img = new Image()
img.src = item.dataset.original
// 图片加载完成触发 load 事件
img.onload = function () {
item.src = img.src
}
// 移除属性的话就不会重复加载了
item.removeAttribute('data-original')
item.removeAttribute('lazyload')
}
})
}
// 先调用一次加载最初显示在视窗中的图片
lazyload();
let throttle_lazyload = throttle(lazyload, 300)
document.addEventListener('scroll', throttle_lazyload)
</script>
</body>
</html>

如何渲染几万条数据且不卡住页面?

这也是小册上的,考察了利用文档碎片 (createDocumentFragment) 分批次插入节点

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<ul>
控件
</ul>
<script>
const total = 100000 // 10万条数据
const once = 20 // 每轮插入的数据条目
const loopCount = total / once // 渲染总次数
let countOfRender = 0
let ul = document.querySelector('ul')
function add() {
// 使用文档碎片优化性能
const fragment = document.createDocumentFragment()
for (let i = 0; i < once; i++) {
const li = document.createElement('li')
li.innerText = Math.floor(Math.random() * total)
fragment.appendChild(li)
}
ul.appendChild(fragment)
countOfRender+=1
loop()
}
function loop() {
if (countOfRender < loopCount) {
window.requestAnimationFrame(add) // 使用 requestAnimationFrame 每隔 16ms(浏览器自己选择最佳时间)刷新一次
}
}
</script>
</body>
</html>

记几个 DOM 操作技巧的更多相关文章

  1. js dom 操作技巧

    1.创建元素 创建元素:document.createElement() 使用document.createElement()可以创建新元素.这个方法只接受一个参数,即要创建元素的标签名.这个标签名在 ...

  2. 深度解析JQuery Dom元素操作技巧

    深度解析JQuery Dom元素操作技巧 DOM是一种与浏览器.平台.语言无关的接口,使用该接口可以轻松访问页面中所有的标准组件,这篇文章给大家介绍了JQuery dom元素操作方法,写的十分的全面细 ...

  3. Javascript的DOM操作 - 你真的了解吗?

    摘要 想稍微系统的说说对于DOM的操作,把Javascript和jQuery常用操作DOM的内容归纳成思维导图方便阅读,同时加入性能上的一些问题. 前言 在前端开发的过程中,javascript极为重 ...

  4. Jquery数组操作技巧

    Jquery对数组的操作技巧. 1. $.each(array, [callback]) 遍历[常用]  解释: 不同于例遍 jQuery 对象的 $.each() 方法,此方法可用于例遍任何对象(不 ...

  5. jQuery2.x源码解析(DOM操作篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) jQuery这个类库最为核心重要的功能就是DOM ...

  6. (转)Javascript的DOM操作 - 性能优化

    转载:https://my.oschina.net/blogshi/blog/198910 摘要: 想稍微系统的说说对于DOM的操作,把Javascript和jQuery常用操作DOM的内容归纳成思维 ...

  7. 在没有DOM操作的日子里,我是怎么熬过来的(中)

    前言 继上篇推送之后,在掘金.segmentfault.简书.博客园等平台上迅速收到了不俗的反馈,大部分网友都留言说感同身受,还有不少网友追问中篇何时更新.于是,闰土顺应呼声,在这个凛冽的寒冬早晨,将 ...

  8. JS的DOM操作 - 你真的了解吗?

    摘要 想稍微系统的说说对于DOM的操作,把Javascript和jQuery常用操作DOM的内容归纳成思维导图方便阅读,同时加入性能上的一些问题. 前言 在前端开发的过程中,javascript极为重 ...

  9. react的非DOM操作

    非dom属性?dangerouslySetInnerHTML,ref,key非dom标准属性,也就是说dom标准里面没有规定的属性,react引入了三个非dom属性,如上. dangerouslySe ...

随机推荐

  1. 跟着大彬读源码 - Redis 4 - 服务器的事件驱动有什么含义?(上)

    众所周知,Redis 服务器是一个事件驱动程序.那么事件驱动对于 Redis 而言有什么含义?源码中又是如何实现事件驱动的呢?今天,我们一起来认识下 Redis 服务器的事件驱动. 对于 Redis ...

  2. springcloud入门系列

    关于springcloud 1.写在前面 写着写这,不知不觉springcloud写了7,8篇了,今天把文章分下类,写下感受及后面的计划吧. (1)springcloud中最最重要的是eureka注册 ...

  3. 使用SVG symbols建立图标系统完整指南

    从最开始的使用img图片,到后来的使用css sprite来减少服务器请求,再到流行的图形字体化图标Iconfont.现在,一种全新的图标使用方式开始流行了起来--SVG symbols图标. 工作原 ...

  4. Samba与nfs与ftp

    Samba服务 samba是一个网络服务器,用于Linux和Windows之间共享文件. samba端口号 samba (启动时会预设多个端口) 数据传输的TCP端口 139.445 进行NetBIO ...

  5. iconfontのsymbol的使用

    iconfontのsymbol的使用 iconfont三种方式的优缺点 unicode 优点: 1.兼容性最好,支持ie6+ 2.支持按字体的方式去动态调整图标大小,颜色等等 缺点: 1.不支持多色图 ...

  6. 2019年7月20日 - LeetCode0002

    https://leetcode-cn.com/problems/add-two-numbers/submissions/ 我的方法: /** * Definition for singly-link ...

  7. C#编程.函数.参数

    详细内容请参见<C#入门经典(第4版)>p101页 1.参数匹配 在调用函数时,必须使参数与函数定义中指定的参数完全匹配,这意味着要匹配参数的类型.个数.和顺序. 注:函数签名由函数的名称 ...

  8. 必懂的wenpack优化

    webpack优化 1.production 模式打包自带优化 tree shaking tree shaking是一个术语.通常用于打包时移除js中未引用的代码(dead-code),它依赖于ES6 ...

  9. vue使用video.js解决m3u8视频播放格式

    今天被这个关于m3u8视频播放不了搞了一下午,这个项目所有的视频流都是m3u8格式的,后台给我们返回的都是m3u8格式的视频流,解决了好长时间,看了好多博客,只有这个博客给我点启发,去解决这个问题,请 ...

  10. 消费端如何保证消息队列MQ的有序消费

    消息无序产生的原因 消息队列,既然是队列就能保证消息在进入队列,以及出队列的时候保证消息的有序性,显然这是在消息的生产端(Producer),但是往往在生产环境中有多个消息的消费端(Consumer) ...