快速搞定前端Javascript面试课程笔记(五)—JS Web API—DOM、BOM、事件

347 阅读5分钟

课程来源: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来判断是否是触发元素

————————————————————————————————————————