课程来源:coding.imooc.com/learn/list/…
从JS基础知识到JS Web API
- JS基础知识,规定语法(ECMA 262标准)
- JS Web API,网页操作的API(W3C标准)
- 前者是后者的基础,两者结合才能真正实际应用
前言
- Vue和React框架应用广泛,封装了DOM操作
- 但DOM操作一直都是前端工程师的基础、必备知识
- 只会vue而不懂DOM操作的前端程序员,不会长久
题目
- DOM是哪种数据结构
- DOM操作的常用API
- attr 和 property 的区别
- 一次性插入多个 DOM 节点,考虑性能
知识点
- DOM 本质
- DOM 节点操作
- DOM 结构操作
- DOM 性能
一、DOM(Document Object Model)的本质
从html语言的文件解析出来的一棵树。
二、DOM 节点操作
- 获取 DOM 节点
- attribute
- property
const div1 = document.getElementById('div1') // 元素
console.log('div1', div1)
const divList = document.getElementsByTagName('div') // 集合
console.log(divList.length)
console.log(divList[0])
const containerList = document.getElementsByClassName('container') // 集合
const pList = document.querySelectorAll('p') //集合
DOM 节点的 property
const pList = document.querySelectorAll('p')
const p = pList[0]
console.log( p.style.width ) // 获取样式
p.style.width = '100px' // 修改样式
console.log( p.className ) // 获取 class
p.className = 'p1' // 修改 class
// 获取 nodeName 和 nodeType
console.log(p.nodeName)
console.log(p.nodeType) // 1(一般DOM元素节点的类型是1,文本元素#text是3)
property 形式是一种通过获取或修改JS属性的方式去改变页面样式或页面渲染结构的一种形式。
DOM 节点的 attribute
const pList = document.querySelectorAll('p')
const p = pList[0]
p.getAttribute('data-name')
p.setAttribute('data-name', 'imooc')
console.log( p.setAttribute('data-name') ) // 'imooc'
p.getAttribute('style')
p.setAttribute('style', 'font-size: 30px;')
区别:
- property形式是对DOM元素的JS变量做的修改
- property:修改对象属性,不会体现到html标签结构中
- attribute是对DOM结构的节点属性做的修改,真正能作用到这个节点属性上去的
- attribute:修改html属性,会改变html标签结构
- 两者都有可能引起DOM重新渲染
三、DOM结构操作
- 新增/插入节点
const div1 = document.getElementById('div1')
// 添加新节点/新建节点
const p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
// 添加新创建的元素/插入节点
div1.appendChild(p1)
// 移动已有节点
const p2 = document.getElementById('p2')
div1.appendChild(p2)
- 获取子元素列表,获取父元素
// 获取子元素列表
const div1 = document.getElementById('div1')
const child = div1.childNodes
const div1ChildNodesP = Array.prototypt.slice.call(div1.childNodes).filter(child => {
if(child.nodeType === 1) {
return true
}
return false
})
console.log('div1ChildNodesP', div1ChildNodesP)
// 获取父元素
const div1 = document.getElementById('div1')
const parent = div1.parentNode
- 删除子元素(节点)
const div1 = document.getElementById('div1')
const child = div1.childNodes
div1.removeChild(child[0])
四、DOM 性能
- DOM 操作非常“昂贵”,避免频繁的 DOM 操作
- 对 DOM 查询做缓存
// 不缓存 DOM 查询结果
for(let = 0; i < document.getElementsByTagName('p').length; i++) {
// 每次循环,都会计算 length ,频繁进行 DOM 查询
}
// 缓存 DOM 查询结果
const pList = document.getElementsByTagName('p')
const length = pList.length
for(let i = 0; i < length; i++) {
// 缓存 length ,只进行一次 DOM 查询
}
- 将频繁操作改为一次性操作(合并的处理)
解决方法同题目:一次性插入多个节点,考虑性能(还需考虑DOM节点的缓存)
const listNode = document.getElementById('list')
// 创建一个文档片段,此时还没有插入到 DOM 树中
const frag = document.creatDocumentFragment()
// 执行插入
for(let x = 0; x < 10; x++) {
const li = document.createElement('li')
li.innerHTML = `List item ${x}`
// 先插入到文档片段中
frag.appendChild(li)
}
// 都完成之后,再统一插入到 DOM 树中
listNode.appendChild(frag)
五、问题补充解答
DOM是哪种数据结构
- 树形结构(DOM 树)
DOM 操作常用 API
- DOM 节点操作
- DOM 结构操作
- attribute 和 property
BOM 操作(Browser Object Model)
题目
- 如何识别浏览器的类型
- 分析拆解 url 各个部分
知识点
- navigator
- screen
- location
- history
// navigator
const ua = navigator.userAgent
const isChrome = ua.indeOf('Chrome')
console.log(isChrome)
// screen
console.log(screen.width)
console.log(screen.height)
// location
console.log(location.href)
console.log(location.protocol) // 'http:' 'https:'
console.log(location.pathname) // '/learn/199'
console.log(location.host)
console.log(location.search)
console.log(location.hash)
// history
history.back()
history.forward()
事件
题目
- 编写一个通用的事件监听函数
- 描述事件冒泡的流程
- 无限下拉的图片列表,如何监听每个图片的点击?
知识点
- 事件绑定
- 事件冒泡
- 事件代理
事件绑定
const btn = document.getElementById('btn1')
btn.addEventListener('click', event => {
console.log('clicked')
})
// 通用的事件绑定函数
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
const a = document.getElementById('link1')
bindEvent(a, 'click', event => {
// console.log(event.target) // 获取触发点击的元素
event.preventDefault() // 阻止默认行为
alert('clicked')
})
事件冒泡
<body>
<div id = "div1">
<p id = "p1">激活</p>
<p id = "p2">取消</p>
<p id = "p3">取消</p>
<p id = "p4">取消</p>
</div>
<div id = "div2">
<p id = "p5">取消</p>
<p id = "p6">取消</p>
</div>
</body>
const p1 = document.getElementById('p1')
const body = document.body
bindEvent(p1, 'click', e => {
e.stopPropagation() // 注释掉这一行,来体会事件冒泡(阻止冒泡)
alert('激活')
})
bindEvent(body, 'click', e => {
alert('取消')
})
事件代理
<div id="div1">
<a href="#">a1</a><br>
<a href="#">a2</a><br>
<a href="#">a3</a><br>
<a href="#">a4</a><br>
</div>
<button>
点击增加一个 a 标签
</button>
const div1 = document.getElementById('div1')
div1.addEventListener('click', event => {
event.preventDefault()
const target = event.target
if(target.nodeName === 'A') {
alert(target.innerHTML)
}
})
- 代码简洁
- 减少浏览器内存占用
- 但是,不要滥用
问题补充解答
编写一个通用的事件绑定函数
function bindEvent(elem, type, selector, fn) {
if(fn == null) {
fn = selector
selector = null
}
elem.addEventListener(type, event => {
const target = event.target
if(selector) {
// 需要代理(代理绑定)
if(target.matches(selector)) { // matches用于判断一个DOM元素是否符合于一个CSS选择器
fn.call(target, event)
}
} else{
// 不需要代理(普通绑定)
fn.call(target, event)
}
})
}
// 普通绑定
const a = document.getElementById('link1')
bindEvent(a, 'click', function(event) {
// console.log(event.target) // 获取触发点击的元素
event.preventDefault() // 阻止默认行为
alert(this.innerHTML)
})
// 代理绑定
const div1 = document.getElementById('div1')
bindEvent('div1', 'click', 'a', function(event) {
event.preventDefault()
alert(this.innerHTML)
})
描述事件冒泡的流程
- 基于 DOM 树形结构
- 事件会顺着触发元素向上冒泡
- 应用场景:代理
无限下拉图片列表,如何监听每个图片的点击
- 事件代理
- 用e.target获取触发元素
- 用matches来判断是否是触发元素
————————————————————————————————————————