从JS基础知识到JS Web API
- JS基础知识,规定语法(ECMA262标准)
- JS Web API,网页操作的API(W3C标准)
- 前者是后者的基础,两者结合才能真正实际应用 JS Web API:
- DOM
- BOM
- 事件绑定
- ajax
- 存储
DOM
DOM操作(Document Object Model)
知识点:
- DOM 本质
- DOM 节点操作
- DOM 结构操作
- DOM 性能
(DOM 是哪种数据结构:树(DOM树),DOM 本质是从HTML文件解析出的一棵树)
DOM 节点操作:
获取DOM节点:
const div1 = document.getElementById('div1') // 元素
const divList = document.getElementsByTagName('div') // 标签集合
const containerList = document.geiElementsByClassName('.container') // 集合
const pList = document.querySelectorAll('p') // 集合, css选择器
DOM 节点的property:
(property不是API名,是一种形式,指获取DOM元素,通过修改或获取JS属性方式进行操作)
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
console.log(p.nodeName) // 获取 nodeName 标签节点名称
console.log(p.nodeType) // 获取 nodeType 类型
DOM 节点的 attribute:
(可以通过 getAttribute、setAttribute 两个 API 直接修改HTML结构、修改标签,能真正作用到 DOM 结构中)
property 对 DOM 元素的JS变量属性进行修改,不会对标签产生影响;attribute 修改的是标签属性,对 DOM 节点属性进行修改。
const pList = document.querySelectorAll('p')
const p = pList[0]
p.getAttribute('data-name')
p.setAttribute('data-name', 'imooc')
p.getAttribute('style')
p.setAttribute('style', 'font-size: 30px;')
property 和 attribute:
- property:修改对象属性,不会体现在 html 结构中
- attribute:修改 html 属性,会改变 html结构
- 两者都可能引起 DOM 重新渲染(尽量使用 property) DOM 结构操作
- 新增/插入节点
- 获取子元素列表,获取父元素
- 删除子元素 新增/插入节点:
const div1 = document.getElementById('div1')
// 添加新节点
const newP = document.createElement('p') // 新建节点
newP.innerHTML = 'this is newP'
div1.appendChild(newP) // 添加新创建的元素,插入节点
// 移动已有节点。注意是移动!!!
const p1 = document.getElementById('p1')
div2.appendChild(p1) // 对现有节点直接进行 appendChild,会移动节点
获取子元素列表&获取父元素
// 获取子元素列表
const div1 = document.getElementById('div1')
const childDiv1 = div1.childNodes
// div1.childNodes[0].nodeName 文本元素
// div1.childNodes[0].nodeType 值是3 文本元素,值是1 dom节点
const childArr = Array.prototype.slice.call(childDiv1).filter(child => {
if (child.nodeType === 1) return true
else false
})
// 获取父元素
const div1 = document.getElementById('div1')
const parent = div1.parentNode
删除节点
const div1 = document.getElementById('div1')
const childDiv1 = div1.childNodes
div1.removeChild(child[0])
DOM 性能
- DOM 操作非常“昂贵”,避免频繁的 DOM 操作
- 对 DOM 查询做缓存
- 将频繁操作改为一次性操作
// 不缓存 DOM 查询结果
for (let i = 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 查询
}
将频繁操作改为一次性操作
const listNode = document.getElementById('list')
// 创建一个文档片段,此时还没有插入到 DOM 树中
const frag = document.createDocumentFragment()
// 执行插入
for (let i = 10; i < 10; i++) {
const li = document.createElement('li')
li.innerHTML = `List item ${i}`
frag.appendChild(li) // 先插入文档片段中
}
// 都完成之后,再插入到 DOM 树中
listNode.appendChild(frag)
BOM 操作(Browser Object Model)浏览器对象模型
- navigator 浏览器信息
- screen 屏幕信息
- location 地址信息
- history 前进后退操作
// navigator
const ua = navigator.userAgent // 获取浏览器信息
const isChrome = ua.indexOf('Chrome')
console.log(isChrome)
// screen
console.log(screen.width)
console.log(screen.height)
// location
console.log(location.href) // 整个网址
console.log(location.protocol) // 协议
console.log(location.pathname) // 路径
console.log(location.host) // 域名 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')
})
事件冒泡
- 基于DOM树形结构
- 事件会顺着触发元素向上冒泡
- 应用场景:代理
<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>
<script>
const p1 = document.getElementById('p1')
const body = document.body
bindEvent(p1, 'click', e => {
e.stopPropagation() // 阻止冒泡,注释掉这一行,来体会事件冒泡
console.log('p1 clicked')
})
bindEvent(body, 'click', e => {
e.stopPropagation()
console.log('body clicked')
console.log(e.target) // 获取触发的元素
})
</script>
事件代理
- 代码简洁
- 减少浏览器内存占用
- 但是不要滥用
<div id="div1">
<a href="#">a1<a>
<a href="#">a2<a>
<a href="#">a3<a>
<a href="#">a4<a>
<button>加载更多...</button>
</div>
const div1 = document.getElementById('div1')
bindEvent(div1, 'click', 'a', event => {
event.prevnetDefault()
const target = event.target
if(target.nodeName === 'a') {
console.log(target.innerHTML)
}
})
通用的事件监听函数
const bindEvent(elem, type, selector, fn) {
if (fn == null) {
fn = selector
selector = null
}
elem.addEventListener(type, e => {
let target
if (selector) {
// 需要代理
target = e.target
if(target.matches(selector)) {
fn.call(target, e)
}
} else {
// 不需要代理,普通绑定
fn.call(target, e)
}
})
}
// 普通绑定
bindEvent(div1, 'click', event => {
event.prevnetDefault()
console.log(event.target.innerHTML)
})
bindEvent(div1, 'click', function (event) {
event.prevnetDefault()
console.log(this.innerHTML)
})
// 代理绑定
bindEvent(div1, 'click', 'a', function (event) {
event.prevnetDefault()
console.log(this.innerHTML)
})
无限下拉图片列表,如何监听每个图片的点击
- 事件代理
- 用e.target获取触发元素
- 用matches来判断是否是触发元素
ajax
XMLHttpRequest
// get 请求
const xhr = new XMLHttpRequest()
xhr.open("GET", "api/", true) //true 异步, false 同步
xhr.onreadystatechange = function () {
// 这里的函数异步执行,可参考之前JS基础中的异步模块
if (xhr.readyState === 4) {
if (xhr.state === 200) {
console.log(xhr.responseText)
console.log(JSON.parse(xhr.responseText))
}
}
}
xhr.send(null)
// post请求
const xhr = new XMLHttpRequest()
xhr.open("POST", "api/", true)
xhr.onreadystatechange = function () {
// 这里的函数异步执行,可参考之前JS基础中的异步模块
if (xhr.readyState === 4) {
if (xhr.state === 200) {
console.log(xhr.responseText)
console.log(JSON.parse(xhr.responseText))
}
}
}
const postData = {
uaerName: 'xxx',
password: 'xxx'
}
xhr.send(JSON.stringify(postData)) // JSON.stringify字符串
xhr.readyState
- 0 — (未初始化)还没有调用send()方法
- 1 — (载入)已调用send()方法,正在发送请求
- 2 — (载入完成)send()方法执行完成,已经接收到全部响应内容
- 3 — (交互)正在解析响应内容
- 4 — (完成)响应内容解析完成,可以在客户端调用 xhr.state
- 2xx — 表示成功处理请求,如200
- 3xx — 需要重定向,浏览器直接跳转,如301 302 304(301 永久重定向,302 临时重定向,304 资源位改变)
- 4xx — 客户端请求错误,如404 403 (403客户端无权限)
- 5xx — 服务端错误
跨域
什么是跨域(同源策略): - ajax 请求时,浏览器要求当前网页和server必须同源
- 同源:协议、域名、端口,三者必须一致
- 加载图片、css、js可无视同源策略:
<img src="跨域的图片地址"/>
<link href="跨域的css地址"/>
<script src="跨域的js地址"></script>
// <img/>可用于统计打点,可使用第三方统计服务
// <link/><script>可使用CDN,CDN一般都是外域
// <script>可实现JSONP
- 所有的跨域,都必须经过server端允许和配合
- 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
跨域的实现方式:JSONP、CORS
JSONP - script 可绕过跨域
- 服务器可以任意动态拼接数据返回,只要符合html格式要求
- 所以,script就可以获得跨域的数据,只要服务端愿意返回
<script>
window.callbank = function (data) {
// 这是我们跨域得到的信息
console.log(data)
}
</script>
<script src="https://xx.com/xx.js"></script>
// 将返回callbank({数据})
<script>
// callbank可以改名
window.abc = function (data) {
console.log(data)
}
</script>
<script src="https://xx.com/xx.js?callbank=abc"></script>
jQuery实现jsonp
$.ajax({
url: 'xxxx',
dataType: 'jsonp',
jsonpCallback: 'callbank',
success: function (data) {
console.log(data)
}
})
CORS — 服务器设置http header
// 第二个参数填写允许跨域的域名称,不建议直接写"*"
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080")
response.setHeader("Access-Control-Allow-Headers", "X-Requested-Width")
response.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")
// 接收跨域的cookie
response.setHeader("Access-Control-Allow-Credentials", "true")
手写简易 ajax
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(xhe.responseText))
} else if (xhr.state === 404) {
reject(new Error('404 not found'))
}
}
}
xhr.send(null)
})
return p
}
const url = 'xxxx'
ajax(url)
.then(res => console.log(res))
.catch(err => console.error(err))
jQuery 完整 ajax 示例
$.ajax({
// 请求方式
type: "POST",
// 请求的媒体类型
contentType: "application/json;charset=UTF-8",
// 请求地址
url: 'xxxx',
// 数据,json字符串
data: JSON.stringify(list),
// 请求成功
success: function (result) {
console.log(result)
},
//请求失败,包含具体的错误信息
error: function (err) {
console.log(err)
}
})
fetch
fetch(url)
.then(function (response) {
return response.json()
})
.then(function (myJson)) {
console.log(myJson)
}
axios
axios.get(url, params: {username: xxx})
.then(function (response) {
console.log(response)
})
.catch(function (error) {
console.log(error)
})
.then(function () {
// always executed
})
axios.post(url, {username: xxx})
.then(function (response) {
console.log(response)
})
.catch(function (error) {
console.log(error)
})
存储
cookie缺点:
- 存储大小,最大4kb
- http请求时需要发送到服务端,增加请求数据量
- 只能用document.cookie=""来修改,只能追加不能覆盖 localStorage 和 sessionStorage:
- HTML5专门为存储而设计,最大可存5M
- API简单易用 setItem getItem
- 不会随着 http 请求被发送出去
- localStorage 数据会永久存储,除非代码或手动删除
- sessionStorage 数据只存在于当前会话,浏览器关闭则清空