记几个 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 ...
随机推荐
- Java基础之回味finally
平时大家try…catch…finally语句用的不少,知道finally块一定会在try…catch..执行结束时执行,但是具体是在什么时候执行呢,今天我们一起来看下. public static ...
- ElasticStack学习(八):ElasticSearch索引模板与聚合分析初探
一.Index Template与Dynamic Template的概念 1.Index Template:它是用来根据提前设定的Mappings和Settings,并按照一定的规则,自动匹配到新创建 ...
- Element-ui DatePicker显示周数
1.场景描述 我们公司是做电商的,运营的工作指标都是按周来定的,所以他们对周特别敏感,希望我们能在日期选择器上显示周数.刚接到这个需求时,心中很不乐意,因为Element-ui的日期选择器根本不支持显 ...
- re模块:模式匹配与正则表达式
一.用正则表达式查找文本模式 正则表达式,简称regex,是文本模式的描述方法.比如\d是一个正则表达式,用于表示一位0~9的数字.在一个模式后面加上花括号包围的数字n(如{n}),表示匹配这个模式n ...
- 洛谷P2598 [ZJOI2009]狼和羊的故事 题解
题目链接: https://www.luogu.org/problemnew/show/P2598 分析: 我们知道此题的目的是将狼和羊分割开,很容易想到狼在S,羊在T中. 首先,我们可以在狼,羊,空 ...
- Excel催化剂开源第17波-VSTO开发之ADO.Net访问Sqlserver
在Excel催化剂中,独树一帜地推出了Excel与数据库交互功能,目前仅实现了对Sqlserver的交互,在通用型插件中仅此一家,别无其他. 为何会出现这样的局面呢,原因大概有以下几大方面: 和数据库 ...
- 深入了解数据校验:Java Bean Validation 2.0(JSR380)
每篇一句 吾皇一日不退役,尔等都是臣子 相关阅读 [小家Java]深入了解数据校验(Bean Validation):基础类打点(ValidationProvider.ConstraintDescri ...
- MySql的数据库优化到底优化啥了都(3)
嘟嘟在上两个文章里面简单粗糙的讲了讲关于MySql存储引擎的一些特性以及选择.个人感觉如果面试官给我机会的话,至少能说个10分钟了吧.只可惜有时候生活就是这样:骨感的皮包骨头了还在那美呢.牢骚两句,北 ...
- static import和import的区别
import static静态导入是JDK1.5中的新特性.一般我们导入一个类都用 import com.....ClassName;而静态导入是这样:import static com.....Cl ...
- SSM-员工管理系统Demo---带分页和校验(含源码)
页面展示: 前端JSP: <%@ page language="java" contentType="text/html; charset=UTF-8" ...