记几个 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 ...
随机推荐
- .NET Core 学习资料精选:入门
开源跨平台的.NET Core,还没上车的赶紧的,来不及解释了-- 本系列文章,主要分享一些.NET Core比较优秀的社区资料和微软官方资料.我进行了知识点归类,让大家可以更清晰的学习.NET Co ...
- SQL Server 根据日期分组、 根据时间段分组(每三个小时一组)
所用数据表: 一.根据日期分组 1. 使用convert() 函数方式 --根据年月 ),CreatTime,)日期,COUNT(*) 次数,sum(Money)总数 from Orders ),Cr ...
- Python面向对象知多少?
问题场景 小王是某游戏公司的新入职的职员,有一天,组长交给了他一个任务,为一个即将准开发的一款游戏设计一些人物角色,其中包括多种职业,如牧师.战士和法师等等.每种职业都有一些自身独有的属性和技能,但是 ...
- Centos7.4 的yum源库配置。
http://mirrors.163.com/.help/centos.html https://www.cnblogs.com/mchina/archive/2013/01/04/2842275.h ...
- Dom4J的基本使用
初始化数据 <?xml version="1.0" encoding="UTF-8"?> <RESULT> <VALUE> ...
- 关于Markdown编辑器的基本使用规则
@TOC 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页.如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown ...
- Netty-Pipeline深度解析
首先我们知道,在NIO网络编程模型中,IO操作直接和channel相关,比如客户端的请求连接,或者向服务端发送数据, 服务端都要从客户端的channel获取这个数据 那么channelPipeline ...
- Python连载24-函数list&read&seek
一. 函数list (1)定义:用打开的文件作为参数,把文件内的每一行内容作为一个元素 (2)格式:list(文件) (3)例子: with open(r"test01.txt", ...
- linux初学者-磁盘加密篇
linux初学者-磁盘加密篇 因为保密需要,一般系统中会在文件和磁盘中进行加密,但是文件的加密比较容易破解,不安全.所以在特殊需要下,会对磁盘进行加密,磁盘加密后在磁盘损坏的同时,其中的数据也会损坏, ...
- java - 多态实现机制
Java提供了编译时多态和运行时多态两种多态机制.前者是通过方法重载实现的,后者是通过方法的覆盖实现的. 在方法覆盖中,子类可以覆盖父类的方法,因此同类的方法会在父类与子类中有着不同的表现形式. 在J ...