记几个 DOM 操作技巧
使用 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 操作技巧的更多相关文章
- js dom 操作技巧
1.创建元素 创建元素:document.createElement() 使用document.createElement()可以创建新元素.这个方法只接受一个参数,即要创建元素的标签名.这个标签名在 ...
- 深度解析JQuery Dom元素操作技巧
深度解析JQuery Dom元素操作技巧 DOM是一种与浏览器.平台.语言无关的接口,使用该接口可以轻松访问页面中所有的标准组件,这篇文章给大家介绍了JQuery dom元素操作方法,写的十分的全面细 ...
- Javascript的DOM操作 - 你真的了解吗?
摘要 想稍微系统的说说对于DOM的操作,把Javascript和jQuery常用操作DOM的内容归纳成思维导图方便阅读,同时加入性能上的一些问题. 前言 在前端开发的过程中,javascript极为重 ...
- Jquery数组操作技巧
Jquery对数组的操作技巧. 1. $.each(array, [callback]) 遍历[常用] 解释: 不同于例遍 jQuery 对象的 $.each() 方法,此方法可用于例遍任何对象(不 ...
- jQuery2.x源码解析(DOM操作篇)
jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) jQuery这个类库最为核心重要的功能就是DOM ...
- (转)Javascript的DOM操作 - 性能优化
转载:https://my.oschina.net/blogshi/blog/198910 摘要: 想稍微系统的说说对于DOM的操作,把Javascript和jQuery常用操作DOM的内容归纳成思维 ...
- 在没有DOM操作的日子里,我是怎么熬过来的(中)
前言 继上篇推送之后,在掘金.segmentfault.简书.博客园等平台上迅速收到了不俗的反馈,大部分网友都留言说感同身受,还有不少网友追问中篇何时更新.于是,闰土顺应呼声,在这个凛冽的寒冬早晨,将 ...
- JS的DOM操作 - 你真的了解吗?
摘要 想稍微系统的说说对于DOM的操作,把Javascript和jQuery常用操作DOM的内容归纳成思维导图方便阅读,同时加入性能上的一些问题. 前言 在前端开发的过程中,javascript极为重 ...
- react的非DOM操作
非dom属性?dangerouslySetInnerHTML,ref,key非dom标准属性,也就是说dom标准里面没有规定的属性,react引入了三个非dom属性,如上. dangerouslySe ...
随机推荐
- 跟着大彬读源码 - Redis 4 - 服务器的事件驱动有什么含义?(上)
众所周知,Redis 服务器是一个事件驱动程序.那么事件驱动对于 Redis 而言有什么含义?源码中又是如何实现事件驱动的呢?今天,我们一起来认识下 Redis 服务器的事件驱动. 对于 Redis ...
- springcloud入门系列
关于springcloud 1.写在前面 写着写这,不知不觉springcloud写了7,8篇了,今天把文章分下类,写下感受及后面的计划吧. (1)springcloud中最最重要的是eureka注册 ...
- 使用SVG symbols建立图标系统完整指南
从最开始的使用img图片,到后来的使用css sprite来减少服务器请求,再到流行的图形字体化图标Iconfont.现在,一种全新的图标使用方式开始流行了起来--SVG symbols图标. 工作原 ...
- Samba与nfs与ftp
Samba服务 samba是一个网络服务器,用于Linux和Windows之间共享文件. samba端口号 samba (启动时会预设多个端口) 数据传输的TCP端口 139.445 进行NetBIO ...
- iconfontのsymbol的使用
iconfontのsymbol的使用 iconfont三种方式的优缺点 unicode 优点: 1.兼容性最好,支持ie6+ 2.支持按字体的方式去动态调整图标大小,颜色等等 缺点: 1.不支持多色图 ...
- 2019年7月20日 - LeetCode0002
https://leetcode-cn.com/problems/add-two-numbers/submissions/ 我的方法: /** * Definition for singly-link ...
- C#编程.函数.参数
详细内容请参见<C#入门经典(第4版)>p101页 1.参数匹配 在调用函数时,必须使参数与函数定义中指定的参数完全匹配,这意味着要匹配参数的类型.个数.和顺序. 注:函数签名由函数的名称 ...
- 必懂的wenpack优化
webpack优化 1.production 模式打包自带优化 tree shaking tree shaking是一个术语.通常用于打包时移除js中未引用的代码(dead-code),它依赖于ES6 ...
- vue使用video.js解决m3u8视频播放格式
今天被这个关于m3u8视频播放不了搞了一下午,这个项目所有的视频流都是m3u8格式的,后台给我们返回的都是m3u8格式的视频流,解决了好长时间,看了好多博客,只有这个博客给我点启发,去解决这个问题,请 ...
- 消费端如何保证消息队列MQ的有序消费
消息无序产生的原因 消息队列,既然是队列就能保证消息在进入队列,以及出队列的时候保证消息的有序性,显然这是在消息的生产端(Producer),但是往往在生产环境中有多个消息的消费端(Consumer) ...