js web api 基础

DOM(document object model)

vue 和 react 等框架,对 DOM 操作进行了封装,但 DOM 操作是前端开发的基础,只会 vue 不会 DOM 操作的工程师不会长久

题目

  1. DOM 是哪种数据结构
  2. DOM 操作常用的 API
  3. attribute 和 property 的区别
  4. 一次性插入多个节点,考虑性能

知识点

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]

// property形式
p1.style.width = '100px'
console.log(p1.style.width)
p1.className = 'red'
console.log(p1.style.className)

console.log(p1.nodeName) // p
console.log(p1.nodeType) // 1

attribute 和 property

  1. property: 修改对象属性,不会体现到 HTML 结构中
  2. attribute: 修改 HTML 属性,会改变 HTML 结构
  3. 两者都可能引起 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)

// 对于现有节点进行appendChild,该节点会移动
const div2 = document.getElementById("div2")
const p1 = document.getElementById("p1")
div2.appendChild(p1)
// div1中不再有p1,已经移动到div2中

获取子元素列表,获取父元素

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)
// text节点的nodetype=3,p节点的nodetype=1

// 获取父元素
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
// 不缓存 DOM 查询结果
for(let i = 0;i<document.getElementById('div1').length;i++){
// 每次循环都要计算length,频繁进行 DOM 查询
}

// 缓存 DOM 查询结果
let list = document.getElementById('div1')
const l = list.length
for(let i = 0;i<l;i++){
// 缓存length,只进行一次 DOM 查询
}

将频繁操作改为一次性操作

1
2
3
4
5
6
7
8
9
10
11
12
13
let list = document.getElementById('div1')

// 创建一个文档片段,此时还没有插入到 DOM 树中
const frag = document.createDocumentFragment()

// 执行插入
for(let i = 0;i<10;i++){
const li = document.createElement("li")
li.innerHTML = "list item" + i
frag.appendChild(li)
}
// 都完成之后再插入 DOM 树中
list.appendChild(frag)

BOM(browser object model)

题目

  1. 如何识别浏览器类型
  2. 分析拆解url各个部分

知识点

1
2
3
4
const ua = navigator.userAgent
const isChrome = ua.indexOf('Chrome')
console.log(isChrome)
// 87

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) // http: 还是 https:
console.log(location.pathname) // 路径
console.log(location.search) // 查询参数
console.log(location.hash) // #后面的内容
console.log(location.host) // 当前 url 的主机名称和端口号

history(前进后退信息)

1
2
history.back() // 后退
history.forward() // 前进

事件绑定

题目

  1. 编写一个通用的事件监听函数
  2. 描述事件冒泡流程
  3. 无限下拉的图片列表,如何监听每个图片的点击
    • 事件代理
    • 用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. 减少浏览器内存使用
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

题目

  1. 手写一个简易的ajax
  2. 跨域的常用实现方式

知识点

  1. XMLHttpRequest
  2. 状态码
  3. 跨域,同源策略,跨域解决方案

XMLHttpRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// get请求
const xhr = new XMLHttpRequest()
xhr.open("GET", "/api", false)
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
// 0,还未初始化,未调用send方法
// 1,已调用send,正在发送请求
// 2,send执行完成,已接收到全部响应内容
// 3,正在解析响应内容
// 4,解析完成,可在客户端调用
if(xhr.status === 200){
// 2xx,成功
// 3xx,重定向,浏览器直接跳转
// 4xx,客户端请求错误
// 5xx,服务端错误
JSON.parse(xhr.responseText)
}
}
}
xhr.send(null)
// post发送数据时,原生的不能发送json,只能发送字符串

跨域

  1. 什么是跨域(同源策略)

ajax请求时,浏览器要求当前网页和server必须同源(安全)
同源:协议、域名、端口三者必须一致
前端:http://a.com:8080/ server:https://b.com/api/xxx

加载图片, css, js 可无视同源策略

  • 图片<img />可用于统计打点,可使用第三方统计服务
  • <link > <script> 可使用CDN,CDN一般都是外域
  • <script> 可使用JSONP

所有跨域,都必须经过server端的允许和配合
未经过server端就允许跨域,说明浏览器有漏洞,危险信号

  1. 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})
1
2
3
4
abc{
a: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)
}
})
  1. 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")

// 接收跨域的cookie
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))

跨域的实现方式

  1. JSONP
  2. CORS

ajax常用插件

  1. jquery
  2. fetch()

fetch只有在网络故障会返回reject,其余都是resolve
3. axios

存储

题目

  1. 描述cookie localStorage sessionStorage 区别

知识点

  • 本身用于浏览器和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')