DOM(document object model)
vue 和 react 等框架,对 DOM 操作进行了封装,但 DOM 操作是前端开发的基础,只会 vue 不会 DOM 操作的工程师不会长久
题目
- DOM 是哪种数据结构
- DOM 操作常用的 API
- attribute 和 property 的区别
- 一次性插入多个节点,考虑性能
知识点
DOM 本质
从HTML文件解析后,在浏览器内存中的一棵树
DOM 节点操作
获取节点
1 2 3 4 5 6 7 8 9
| <div class="container"> <div id="div1"> <p id="p1">1</p> <p>2</p> <p>3</p> </div> <div id="div1"> </div> </div>
|
1 2 3 4 5 6 7 8
| const div1 = document.getElementById("div1")
const divList = document.getElementByTagName("div") console.log(divList.length
const containerList = document.getElementByClassName(".container")
const pList = document.querySelectorAll('p')
|
attribute
1 2 3 4 5
| const pList = document.querySelectorAll('p') const p1 = pList[0]
p1.setAttribute('data-name', 'hello') console.log(p1.getAttribute('data-name'))
|
property
1 2 3 4 5 6 7 8 9 10 11
| const pList = document.querySelectorAll('p') const p1 = pList[0]
p1.style.width = '100px' console.log(p1.style.width) p1.className = 'red' console.log(p1.style.className)
console.log(p1.nodeName) console.log(p1.nodeType)
|
attribute 和 property
- property: 修改对象属性,不会体现到 HTML 结构中
- attribute: 修改 HTML 属性,会改变 HTML 结构
- 两者都可能引起 DOM 重新渲染
DOM 结构操作
新增/插入节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const div1 = document.getElementById("div1")
const p1 = document.createElement('p') p1.innerHTML = 'this is p1'
div1.appendChild(p1)
const div2 = document.getElementById("div2") const p1 = document.getElementById("p1") div2.appendChild(p1)
|
获取子元素列表,获取父元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const div1 = document.getElementById("div1") const child = div1.childNodes const childP = Array.prototype.slice.call(child).filter(child=>{ if(child.nodeType === 1){ return true } return false }) console.log(childP)
const div1 = document.getElementById("div1") const parent = div1.parentNode
|
删除元素
1 2
| div1.removeChild(child[0])
|
DOM 性能
避免频繁的 DOM 操作
对 DOM 查询做缓存
1 2 3 4 5 6 7 8 9 10 11
| for(let i = 0;i<document.getElementById('div1').length;i++){ }
let list = document.getElementById('div1') const l = list.length for(let i = 0;i<l;i++){ }
|
将频繁操作改为一次性操作
1 2 3 4 5 6 7 8 9 10 11 12 13
| let list = document.getElementById('div1')
const frag = document.createDocumentFragment()
for(let i = 0;i<10;i++){ const li = document.createElement("li") li.innerHTML = "list item" + i frag.appendChild(li) }
list.appendChild(frag)
|
BOM(browser object model)
题目
- 如何识别浏览器类型
- 分析拆解url各个部分
知识点
navigator(浏览器的信息)
1 2 3 4
| const ua = navigator.userAgent const isChrome = ua.indexOf('Chrome') console.log(isChrome)
|
screen(屏幕信息)
1 2
| console.log(screen.width) console.log(screen.height)
|
location(地址信息)
1 2 3 4 5 6
| console.log(location.href) console.log(location.protocol) console.log(location.pathname) console.log(location.search) console.log(location.hash) console.log(location.host)
|
history(前进后退信息)
1 2
| history.back() history.forward()
|
事件绑定
题目
- 编写一个通用的事件监听函数
- 描述事件冒泡流程
- 无限下拉的图片列表,如何监听每个图片的点击
- 事件代理
- 用e.target获取触发元素
- 用match来判断是否是触发元素
知识点
事件绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const btn = document.getElementById('btn') btn.addEventListener('click', event => { console.log('clicked') })
function bindEvent(elem, type, fn){ elem.addEventListener(type, fn) } const a = document.getElementById('link') bindEvent(a, 'click', e => { e.preventDefault() console.log(e.target) alert('clicked') })
|
事件冒泡
1 2 3 4 5 6 7 8 9 10
| <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>
|
1 2 3 4 5 6 7 8 9 10 11
| const body = document.body bindEvent(body, 'click', e => { console.log('body clicked') console.log(e.target) })
const div2 = document.getElementById('div2') bindEvent(div2, 'click', e => { console.log('div2 clicked') console.log(e.target) })
|
1 2 3 4 5 6 7 8 9 10
| const p1 = document.getElementById('p1') bindEvent(p1, 'click', e => { e.stopPropagation() console.log('激活') })
const body = document.body bindEvent(body, 'click', e => { console.log('取消') })
|
事件代理
1 2 3 4 5 6 7
| <div id="div1"> <p id="p1">p1</p> <p id="p2">p2</p> <p id="p3">p3</p> <p id="p4">p4</p> <button>加载更多</button> </div>
|
应用:点击某个p标签,添加一个p标签,由于无法知道到底有多少个p标签,不能给全部p添加点击事件,所以需要事件代理到div1上
1 2 3 4 5 6 7 8
| const div1 = document.getElementById('div1') bindEvent(div1, 'click', e => { e.preventDefault() const target = e.target if(target.nodeName === 'p'){ alert(target.innerHTML) } })
|
- 代码简洁
- 减少浏览器内存使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function bindEvent(elem, type, selector, fn){ if(fn == null){ fn = selector selector = null } elem.addEventListener(type, event => { if(selector){ if(target.matches(selector)){ fn.call(target, event) } }else{ fn.call(target, event) } }) }
|
ajax
题目
- 手写一个简易的ajax
- 跨域的常用实现方式
知识点
- XMLHttpRequest
- 状态码
- 跨域,同源策略,跨域解决方案
XMLHttpRequest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const xhr = new XMLHttpRequest() xhr.open("GET", "/api", false) xhr.onreadystatechange = function(){ if(xhr.readyState === 4){ if(xhr.status === 200){ JSON.parse(xhr.responseText) } } } xhr.send(null)
|
跨域
- 什么是跨域(同源策略)
ajax请求时,浏览器要求当前网页和server必须同源(安全)
同源:协议、域名、端口三者必须一致
前端:http://a.com:8080/ server:https://b.com/api/xxx
加载图片, css, js 可无视同源策略
- 图片
<img />
可用于统计打点,可使用第三方统计服务
<link > <script>
可使用CDN,CDN一般都是外域
<script>
可使用JSONP
所有跨域,都必须经过server端的允许和配合
未经过server端就允许跨域,说明浏览器有漏洞,危险信号
- JSONP
访问一个网址,服务器一定返回的是一个html文件吗
服务器可以任意拼接数据返回,只要符合html格式要求
同理于<script src="https://a.com/getData.js">
<script>
可绕过跨域限制
服务器可以任意动态拼接数据返回
所以,<script>
就可以获得跨域的数据,只要服务端愿意返回
1 2 3 4 5 6 7 8 9 10 11
| <script>
window.abc = function (data){ console.log(data) }
</script>
<script src="https://a.com/getData.js?callback=abc"></script> // 将返回callback({x:100,y:200})
|
jquery实现jsonp
1 2 3 4 5 6 7 8
| $.ajax({ url: 'https://a.com/getData.json', dataType: 'jsonp', jsonpCallback: 'callback', success: function(data){ console.log(data) } })
|
- CORS(服务端支持)
服务器端设置 http header
1 2 3 4 5 6
| response.setHeader("Access-Control-Allow-Origin","http://localhost:8080") response.setHeader("Access-Control-Allow-Headers","X-Requested-With") response.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS")
response.setHeader("Access-Control-Allow-Credentials","true")
|
手写简易ajax
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function ajax(url){ const p = new Promise((resolve,reject)=>{ const xhr = new XMLHttpRequest() xhr.open('GET', url, true) xhr.onreadystatechange = function(){ if(xhr.readyState === 4){ if(xhr.status === 200){ resolve( JSON.parse(xhr.responseText) ) }else if(xhr.status === 404){ reject(new Error('404 not found')) } } } xhr.send(null) }) return p }
const url = '/data.json' ajax(url) .then(res => console.log(res)) .catch(err => console.log(err))
|
跨域的实现方式
- JSONP
- CORS
ajax常用插件
- jquery
- fetch()
fetch只有在网络故障会返回reject,其余都是resolve
3. axios
存储
题目
- 描述cookie localStorage sessionStorage 区别
知识点
cookie
- 本身用于浏览器和server通讯
- 被“借用”到本地存储来
- 可通过document.cookie = ‘…’来修改
1 2 3 4 5 6 7
| var x = document.cookie;
document.cookie="username=John Smith; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=/";
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
|
缺点:
- 最大4kb
- http请求时需要发送到服务端,增加请求数据量
- 只能通过document.cookie = ‘…’来修改,太简陋
localStorage sessionStorage
参考
- HTML5专门为存储而设计,最大可存5M
- API更简单易用,setItem, getItem
- 不会随着http请求被发送出去
localStorage
数据会永久存储,除非代码或手动删除
1 2
| localStorage.setItem('a','100') localStorage.getItem('a')
|
sessionStorage
数据只存在于当前会话,浏览器关闭则清空
1 2
| sessionStorage.setItem('b','200') sessionStorage.getItem('b')
|