提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、问题引入
二、知识点详解
1.event loop(事件循环/事件轮询)
event loop的执行过程
以下代码为例:
console.log('Hi')
setTimeout(function cb1(){
console.log('cb1')
},5000)
console.log('Bye')
执行过程:
1.console.log(‘Hi’)入栈
2.立即执行console.log(‘Hi’),出栈,并在浏览器中输出Hi
3.setTimeout
入栈
4.setTimeout将其设置的定时器timer
添至Web APIs
中,然后出栈(setTimeout为异步,同步执行完再执行)
5.console.log(‘Bye’)入栈
6.console.log(‘Bye’)出栈,并在浏览器窗口输出Bye
7.所有的同步代码执行完毕,异步代码注册回调函数,进入Callback队列。Event loop开始运行,查询回调任务队列
8.如果回调队列中不为空,则入栈(函数cb1入栈)
9.函数体console.log(‘cb1’)入栈
10.console.log(‘cb1’)出栈,在浏览器窗口输出cb1
11.函数cb1出栈,Event loop继续查询回调队列,为空,代码执行完毕
DOM和event loop
DOM事件中的Event loop分析
event loop机制(画图)
2.Promise
Promise参考1
Promise参考2
Promise 是 异步 编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法
三种状态
Promise对象代表一个异步操作,有3种状态
- pending(进行中)
- fulfilled(已成功)
- rejected(已失败)
只能由pending——>fulfilled或pending——>rejected且状态不可逆,一旦改变就不会在变
// 刚定义时,状态默认为 pending
const p1 = new Promise((resolve, reject) => {
})
// 执行 resolve() 后,状态变成 fulfilled
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
})
})
// 执行 reject() 后,状态变成 rejected
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject()
})
})
// 直接返回一个 resolved 状态
Promise.resolve(100)
// 直接返回一个 rejected 状态
Promise.reject('some error')
状态的表现和变化
- pending状态,不会触发then和catch
- fulfilled状态,触发后续的then回调函数
- rejected状态,触发后续的catch回调函数
then和catch改变状态
- then正常返回fulfilled,里面有报错则返回rejected
- catch正常返回fulfilled,里面有报错则返回rejected
// then() 一般正常返回 fulfilled 状态的 promise
Promise.resolve().then(() => {
return 100
})
// then() 里抛出错误,会返回 rejected 状态的 promise
Promise.resolve().then(() => {
throw new Error('err')
})
// catch() 不抛出错误,会返回 fulfilled 状态的 promise
Promise.reject().catch(() => {
console.error('catch some error')
})
// catch() 抛出错误,会返回 rejected 状态的 promise
Promise.reject().catch(() => {
console.error('catch some error')
throw new Error('err')
})
// 第一题
Promise.resolve().then(() => {
console.log(1) //1
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3) //3
})
// 第二题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
console.log(1) //1
throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
console.log(2) //2
}).then(() => {
console.log(3) //3
})
// 第三题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
console.log(1) //1
throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
console.log(2) //2
}).catch(() => {
console.log(3)
})
手写Promise
class MyPromise {
state = 'pending' // 状态,'pending' 'fulfilled' 'rejected'
value = undefined // 成功后的值
reason = undefined // 失败后的原因
resolveCallbacks = [] // pending 状态下,存储成功的回调
rejectCallbacks = [] // pending 状态下,存储失败的回调
constructor(fn) {
const resolveHandler = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.resolveCallbacks.forEach(fn => fn(this.value))
}
}
const rejectHandler = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.rejectCallbacks.forEach(fn => fn(this.reason))
}
}
try {
fn(resolveHandler, rejectHandler)
} catch (err) {
rejectHandler(err)
}
}
then(fn1, fn2) {
fn1 = typeof fn1 === 'function' ? fn1 : (v) => v
fn2 = typeof fn2 === 'function' ? fn2 : (e) => e
if (this.state === 'pending') {
const p1 = new MyPromise((resolve, reject) => {
this.resolveCallbacks.push(() => {
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch (err) {
reject(err)
}
})
this.rejectCallbacks.push(() => {
try {
const newReason = fn2(this.reason)
reject(newReason)
} catch (err) {
reject(err)
}
})
})
return p1
}
if (this.state === 'fulfilled') {
const p1 = new MyPromise((resolve, reject) => {
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch (err) {
reject(err)
}
})
return p1
}
if (this.state === 'rejected') {
const p1 = new MyPromise((resolve, reject) => {
try {
const newReason = fn2(this.reason)
reject(newReason)
} catch (err) {
reject(err)
}
})
return p1
}
}
// 就是 then 的一个语法糖,简单模式
catch(fn) {
return this.then(null, fn)
}
}
MyPromise.resolve = function (value) {
return new MyPromise((resolve, reject) => resolve(value))
}
MyPromise.reject = function (reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
MyPromise.all = function (promiseList = []) {
const p1 = new MyPromise((resolve, reject) => {
const result = [] // 存储 promiseList 所有的结果
const length = promiseList.length
let resolvedCount = 0
promiseList.forEach(p => {
p.then(data => {
result.push(data)
// resolvedCount 必须在 then 里面做 ++
// 不能用 index
resolvedCount++
if (resolvedCount === length) {
// 已经遍历到了最后一个 promise
resolve(result)
}
}).catch(err => {
reject(err)
})
})
})
return p1
}
MyPromise.race = function (promiseList = []) {
let resolved = false // 标记
const p1 = new Promise((resolve, reject) => {
promiseList.forEach(p => {
p.then(data => {
if (!resolved) {
resolve(data)
resolved = true
}
}).catch((err) => {
reject(err)
})
})
})
return p1
}
3.async/await
async/await 是基于Promise的解决异步的最终方案。
- async是一个加在函数前的修饰符,被async定义的函数会默认返回一个Promise对象resolve的值。因此对async函数可以直接then,返回值就是then方法传入的函数。
- await 也是一个修饰符,只能放在async定义的函数内。可以理解为等待。await修饰的如果是Promise对象:可以获取Promise中返回的内容(resolve或reject的参数),且取到值后语句才会往下执行;如果不是Promise对象:把这个非promise的东西当做await表达式的结果。
async/await和Promise的关系
- async 函数返回结果都是 Promise 对象(如果函数内没返回 Promise ,则自动封装一下)
async function fn2() {
return new Promise(() => {})
}
console.log( fn2() )
async function fn1() {
return 100
}
console.log( fn1() ) // 相当于 Promise.resolve(100)
- await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行
- await 后跟非 Promise 对象:会直接返回
(async function () {
const p1 = new Promise(() => {})
await p1
console.log('p1') // 不会执行
})()
(async function () {
const p2 = Promise.resolve(100)
const res = await p2
console.log(res) // 100
})()
(async function () {
const res = await 100
console.log(res) // 100
})()
(async function () {
const p3 = Promise.reject('some err')
const res = await p3
console.log(res) // 不会执行
})()
- try…catch 捕获 rejected 状态
(async function () {
const p4 = Promise.reject('some err')
try {
const res = await p4
console.log(res)
} catch (ex) {
console.error(ex)
}
})()
总结
- async 封装 Promise
- await 处理 Promise 成功
- try…catch 处理 Promise 失败
异步的本质
await 是同步写法,但本质还是异步调用
async function async1 () {
console.log('async1 start')
await async2() //先执行async2()函数,再看await。下面一行是callback内容
console.log('async1 end') //关键在这一步,它相当于放在callback 中,最后执行
}
async function async2 () {
console.log('async2')
}
console.log('script start')
async1()
console.log('script end') //同步执行完,执行异步 同步=>异步
即,只要遇到了 await ,后面的代码都相当于放在 callback 里。
4.宏任务和微任务
宏任务
- 宏任务:setTimeout setInterval Ajax DOM 事件
微任务
- 微任务:Promise async/await(对于前端来说)
console.log(100)
//宏任务
setTimeout(() => {
console.log(200)
})
//微任务
Promise.resolve().then(() => {
console.log(300)
})
console.log(400)
// 100 400 300 200
event loop和DOM渲染
即:同步=>微任务=>DOM渲染=>宏任务
async function async1 () {
console.log('async1 start')
await async2() //先执行asnync2(),返回了一个promise,故此时await相当于then
console.log('async1 end') //由于await,成为微任务
}
async function async2 () {
console.log('async2')
}
console.log('script start') //1
setTimeout(function(){
console.log('setTimeout') //宏任务
},0)
async1() //执行函数
new Promise(function(resolve){
console.log('promise1') //初始化promise时,传入的函数会立刻被执行
resolve()
}).then(function(){ //微任务
console.log('promise2')
})
console.log('script end')
宏任务和微任务的区别
- 微任务比宏任务执行的更早
再深入思考一下:为何两者会有以上区别,一个在渲染前,一个在渲染后? - 微任务(ES6语法规定的):ES 语法标准之内,JS 引擎来统一处理。即,不与浏览器有任何关于,即可一次性处理完,更快更及时。
- 宏任务(浏览器规定的):ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。
5.Web-API
DOM
DOM 全称是 Document Object Model,也就是文档对象模型。是针对XML的基于树的API。描述了处理网页内容的方法和接口,是HTML和XML的API,DOM把整个页面规划成由节点层级构成的文档。
DOM 是 W3C 的标准; [所有浏览器公共遵守的标准]
DOM 有什么用?就是为了操作 HTML 中的元素,比如说我们要通过 JS 把这个网页的标题改了,直接这样就可以了:
document.title = 'how to make love';
这个 API 使得在网页被下载到浏览器之后改变网页的内容成为可能。
参考
- DOM的本质:浏览器从HTML文件解析出来的一棵树
- DOM的节点操作
获取DOM节点
DOM节点的attribute
attribute:是HTML标签上的某个属性,如id、class、value等以及自定义属性,它的值只能是字符串,关于这个属性一共有三个相关的方法,setAttribute、getAttribute、removeAttribute;
在使用setAttribute的时候,该函数一定接收两个参数,setAttribute(attributeName,value),无论value的值是什么类型都会编译为字符串类型。在html标签中添加属性,本质上是跟在标签里面写属性时一样的,所以属性值最终都会编译为字符串类型。
参考
DOM节点的property
property:是js获取的DOM对象上的属性值,比如a,你可以将它看作为一个基本的js对象。这个节点包括很多property,比如value,className以及一些方法onclik等方法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>dom 演示</title>
<style>
.container {
border: 1px solid #ccc;
}
.red {
color: red;
}
</style>
</head>
<body>
<div id="div1" class="container">
<p id="p1">一段文字 1</p>
<p>一段文字 2</p>
<p>一段文字 3</p>
</div>
<div id="div2">
<img src="https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg"/>
</div>
<ul id="list">
</ul>
<script src="./dom-1.js"></script>
</body>
</html>
const div1 = document.getElementById('div1')
console.log('div1', div1)
const divList = document.getElementsByTagName('div') // 集合
console.log('divList.length', divList.length) //divList.length 2
console.log('divList[1]', divList[1])
const containerList = document.getElementsByClassName('container') // 集合
console.log('containerList.length', containerList.length) //containerList.length 1
console.log('containerList[1]', containerList[1]) //containerList[1] undefined
// const pList = document.querySelectorAll('p')
// console.log('pList', pList)
const pList = document.querySelectorAll('p')
const p1 = pList[0]
// property 形式
p1.style.width = '100px'
console.log( p1.style.width ) //100px
p1.className = 'red'
console.log( p1.className ) //red
console.log(p1.nodeName) //P
console.log(p1.nodeType) // 1
// attribute
p1.setAttribute('data-name', 'imooc')
console.log( p1.getAttribute('data-name') ) //imooc
p1.setAttribute('style', 'font-size: 50px;')
console.log( p1.getAttribute('style') ) //font-size: 50px;
- DOM的结构操作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>dom 演示</title>
<style>
.container {
border: 1px solid #ccc;
}
.red {
color: red;
}
</style>
</head>
<body>
<div id="div1" class="container">
<p id="p1">一段文字 1</p>
<p>一段文字 2</p>
<p>一段文字 3</p>
</div>
<div id="div2">
<img src="https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg"/>
</div>
<ul id="list">
</ul>
<script src="./dom-2.js"></script>
</body>
</html>
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)
// 移动节点
const p1 = document.getElementById('p1')
div2.appendChild(p1)
// 获取父元素
console.log( p1.parentNode )
// 获取子元素列表(普通标签的nodeType===1,div的child除了p之外还有text,故需要过滤)
const div1ChildNodes = div1.childNodes
console.log( div1.childNodes )
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {
if (child.nodeType === 1) {
return true
}
return false
})
console.log('div1ChildNodesP', div1ChildNodesP)
在最后加上div1.removeChild( div1ChildNodesP[0] )时,p2被删除了
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)
// 移动节点
const p1 = document.getElementById('p1')
div2.appendChild(p1)
// 获取父元素
console.log( p1.parentNode )
// 获取子元素列表(普通标签的nodeType===1,div的child除了p之外还有text,故需要过滤)
const div1ChildNodes = div1.childNodes
console.log( div1.childNodes )
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {
if (child.nodeType === 1) {
return true
}
return false
})
console.log('div1ChildNodesP', div1ChildNodesP)
div1.removeChild( div1ChildNodesP[0] )
- DOM性能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>dom 演示</title>
<style>
.container {
border: 1px solid #ccc;
}
.red {
color: red;
}
</style>
</head>
<body>
<div id="div1" class="container">
<p id="p1">一段文字 1</p>
<p>一段文字 2</p>
<p>一段文字 3</p>
</div>
<div id="div2">
<img src="https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg"/>
</div>
<ul id="list">
</ul>
<script src="./dom-2.js"></script>
</body>
</html>
const list = document.getElementById('list')
// 创建一个文档片段,此时还没有插入到 DOM 结构中
const frag = document.createDocumentFragment()
for (let i = 0; i < 20; i++) {
const li = document.createElement('li')
li.innerHTML = `List item ${i}`
// 先插入文档片段中
frag.appendChild(li)
}
// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag)
console.log(list)
BOM
参考文章
BOM(Browser Object Model) 是指浏览器对象模型,是用于描述这种对象与对象之间层次关系的模型,浏览器对象模型提供了独立于内容的、可以与浏览器窗口进行互动的对象结构。BOM由多个对象组成,其中代表浏览器窗口的Window对象是BOM的顶层对象,其他对象都是该对象的子对象。
DOM 是为了操作文档出现的接口,那 BOM 顾名思义其实就是为了控制浏览器的行为而出现的接口。
由于BOM的window包含了document,因此可以直接使用window对象的document属性,通过document属性就可以访问、检索、修改XHTML文档内容与结构。因为document对象又是DOM(Document Object Model)模型的根节点。
可以说,BOM包含了DOM(对象),浏览器提供出来给予访问的是BOM对象,从BOM对象再访问到DOM对象,从而js可以操作浏览器以及浏览器读取到的文档。
BOM的核心是Window,而Window对象又具有双重角色,它既是通过js访问浏览器窗口的一个接口,又是一个Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都以window作为其global对象。
Window对象包含属性:document、location、navigator、screen、history、frames
Document根节点包含子节点:forms、location、anchors、images、links
从window.document已然可以看出,DOM的最根本的对象是BOM的window对象的子对象。
区别:DOM描述了处理网页内容的方法和接口,BOM描述了与浏览器进行交互的方法和接口
navigator
对象:包含大量有关Web浏览器的信息,在检测浏览器及操作系统上非常有用
window.navigator.appCodeName //浏览器代码名
window.navigator.appName //浏览器步伐名
window.navigator.appMinorVersion //浏览器补钉版本
window.navigator.cpuClass //cpu类型 x86
window.navigator.platform //操作体系类型 win32
window.navigator.plugins
window.navigator.opsProfile
window.navigator.userProfile
window.navigator.systemLanguage //客户体系语言 zh-cn简体中文
window.navigator.userLanguage //用户语言,同上
window.navigator.appVersion //浏览器版本(包括 体系版本)
window.navigator.userAgent//用户代理头的字符串表示
window.navigator.onLine //用户否在线
window.navigator.cookieEnabled //浏览器是否撑持cookie
window.navigator.mimeTypes
screen
对象:用于获取某些关于用户屏幕的信息,也可用window.screen引用它
window.screen.width //屏幕宽度
window.screen.height //屏幕高度
window.screen.colorDepth //屏幕颜色深度
window.screen.availWidth //可用宽度(除去任务栏的高度)
window.screen.availHeight //可用高度(除去任务栏的高度)
location
对象:表示载入窗口的URL,也可用window.location引用它
location.href //当前载入页面的完整URL,如http://www.somewhere.com/pictures/index.htm
location.portocol //URL中使用的协议,即双斜杠之前的部分,如http
location.host //服务器的名字,如www.wrox.com
location.hostname //通常等于host,有时会省略前面的www
location.port //URL声明的请求的端口,默认情况下,大多数URL没有端口信息,如8080
location.pathname //URL中主机名后的部分,如/pictures/index.htm
location.search //执行GET请求的URL中的问号后的部分,又称查询字符串,如?param=xxxx
location.hash //如果URL包含#,返回该符号之后的内容,如#anchor1
location.assign("http:www.baidu.com"); //同location.href,新地址都会被加到浏览器的历史栈中
location.replace("http:www.baidu.com"); //同assign(),但新地址不会被加到浏览器的历史栈中,不能通过back和forward访问
location.reload(true | false); //重新载入当前页面,为false时从浏览器缓存中重载,为true时从服务器端重载,默认为false
document.location.reload(URL) //打开新的网页
History
对象
window.history.length //浏览过的页面数
history.back() //在浏览历史里后退一步
history.forward() //在浏览历史里前进一步
history.go(i) //i>0前进,i<0回退
事件
就是文档或浏览器窗口中发生的一些特定的交互瞬间
常见事件分类:
鼠标事件:onclick, ondbclick, onmouseover,onmousedown, onmouseup,onmousemove,onmouseout等
键盘事件:onkeyup,onkeydown,onkeypress...
表单事件:onsubmit,onblur,onfoucs,onchange..
页面事件:onload,onunload,onreload...
事件绑定
事件绑定参考
在 html 元素上将触发事件的行为和事件的相应的程序关联起来的过程就是事件的绑定
绑定事件的三种方法:
1.嵌入dom
<button onclick="open()">按钮</button>
<script>
function open(){
alert(1)
}
</script>
2.直接绑定
<button id="btn">按钮</button>
<script>
document.getElementById('btn').onclick = function(){
alert(1)
}
</script>
3.事件监听
<button id="btn">按钮</button>
<script>
document.getElementById('btn').addEventListener('click',function(){
alert(1)
})
//兼容IE
document.getElementById('btn').attachEvent('click',function(){
alert(1)
})
</script>
事件冒泡
事件冒泡参考
事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。
<div id="outer">
<p id="inner">Click me!</p>
</div>
以上代码在事件冒泡的概念下发生click事件的顺序应该是:
p——>div——>body——>html——>document
一个事件触发后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段:
如上图所示,事件传播分成三个阶段:
捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件;
目标阶段:在目标节点上触发,称为“目标阶段”
冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层;
事件捕获与事件冒泡图示
e.stopPropagation()可以阻止冒泡,如果注释掉这一行,在点击激活后,将打印出
在点击p后将冒泡到body并打印出取消,但这并不是我们所期望的,这时就需要阻止冒泡了
<style>
div {
border: 1px solid #ccc;
margin: 10px 0;
padding: 0 10px;
}
</style>
</head>
<body>
<button id="btn1">一个按钮</button>
<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>
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
const p1 = document.getElementById('p1')
bindEvent(p1, 'click', event => {
event.stopPropagation() // 阻止冒泡
console.log('激活')
})
const body = document.body
bindEvent(body, 'click', event => {
console.log('取消')
console.log(event.target)
})
const div2 = document.getElementById('div2')
bindEvent(div2, 'click', event => {
console.log('div2 clicked')
console.log(event.target)
})
事件代理(委托)
事件代理
事件代理即是把原本需要绑定在子元素的响应事件(click、keydown…)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
优点:
- 可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件就非常棒
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
// ...... 代表中间还有未知数个 li
如上面代码所示,如果给每个li列表项都绑定一个函数,那对内存的消耗是非常大的,因此较好的解决办法就是将li元素的点击事件绑定到它的父元素ul身上,执行事件的时候再去匹配判断目标元素。
- 可以实现当新增子对象时无需再次对其绑定(动态绑定事件)
假设上述的例子中列表项li就几个,我们给每个列表项都绑定了事件;
在很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者删除列表项li元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;
如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。
注:使用“事件委托”时,并不是说把事件委托给的元素越靠近顶层就越好。事件冒泡的过程也需要耗时,越靠近顶层,事件的”事件传播链”越长,也就越耗时。如果DOM嵌套结构很深,事件冒泡通过大量祖先元素会导致性能损失。
<style>
div {
border: 1px solid #ccc;
margin: 10px 0;
padding: 0 10px;
}
</style>
</head>
<body>
<button id="btn1">一个按钮</button>
<div id="div3">
<a href="#">a1</a><br>
<a href="#">a2</a><br>
<a href="#">a3</a><br>
<a href="#">a4</a><br>
<button>加载更多...</button>
</div>
</body>
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)) {
fn.call(target, event)
}
} else {
// 普通绑定
fn.call(target, event)
}
})
}
// 普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {
// console.log(event.target) // 获取触发的元素
event.preventDefault() // 阻止默认行为
alert(this.innerHTML)
})
// 代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', function (event) {
event.preventDefault()
alert(this.innerHTML)
})
Ajax
- ajax 全名
async javascript and XML
(异步JavaScript和XML) - 是
前后台交互
的能⼒ 也就是我们客户端给服务端发送消息的⼯具,以及接受响应的⼯具
- AJAX
不是新的编程语言
,而是一种使用现有标准的新方法。 - AJAX 是与服务器交换数据并更新部分网页的艺术,
在不重新加载整个页面的情况下
。 - 是⼀个
默认异步执⾏机制
的功能,AJAX分为同步(async = false)和异步(async = true)
优势: - 不需要插件的⽀持,原⽣ js 就可以使⽤
- ⽤户体验好(不需要刷新⻚⾯就可以更新数据)
- 减轻服务端和带宽的负担
缺点:搜索引擎的⽀持度不够,因为数据都不在⻚⾯上,搜索引擎搜索不到
操作流程:
具体操作流程:
首先通过PHP页面将数据库中的数据取出
取出后转成json格式的字符串,后利用ajax把字符串返还给前台
再利用json.parse解析通过循环添加到页面上
那么反之,前端的数据可以利用ajax提交到后台
但是后台是没有办法直接把这些数据插入到数据库中,所以要先提交到PHP页面上
最后再由PHP将数据插入到数据库中
XMLHttpRequest
XMLHttpRequest 对象方法描述:
XMLHttpRequest 对象属性描述(用于和服务器交换数据)
与 POST 相比,GET 更简单也更快
,并且在大部分情况下都能用。
然而,在以下情况中,请使用 POST 请求:
1.无法使用缓存文件(更新服务器上的文件或数据库)
2.向服务器发送大量数据(POST 没有数据量限制)
3.发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠
get请求:
post请求:
状态码
跨域(同源策略)
跨域参考
所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
跨域解决方法:
- 设置document.domain解决无法读取非同源网页的 Cookie问题 (此方案仅限主域相同,子域不同的跨域应用场景)
// 两个页面都设置
document.domain = 'test.com';
- 跨文档通信 API:window.postMessage()
- JSONP
JSONP
JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,兼容性好(兼容低版本IE),缺点是只支持get请求,不支持post请求
。
核心思想:网页通过添加一个<script>
元素,向服务器请求 JSON 数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。
CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。
1、普通跨域请求:只需服务器端设置Access-Control-Allow-Origin
2、带cookie跨域请求:前后端都需要进行设置
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
总结
event loop的执行过程:
- 同步代码,一行一行放在Call Stack执行
- 遇到异步,会先“记录”下,等待时机(定时、网络请求等)
- 时机到了,就移动到Callback
- Queue 如Call Stack为空(即同步代码执行完)Event loop开始工作
- 轮询查找CallbackQueue,如有则移动到Call Stack执行
- 然后继续轮询查找
async/await:
- async 封装 Promise
- await 处理 Promise 成功
- try…catch 处理 Promise 失败
宏任务/微任务:
- 宏任务:setTimeout setInterval Ajax DOM 事件
- 微任务:Promise async/await
- 同步=>微任务=>DOM渲染=>宏任务