单线程
- 单线程:只有一个线程,只能做一件事情
- 原因:避免DOM渲染的冲突
- 解决方案:异步
// 循环运行期间,JS执行和DOM渲染暂时卡顿
var i,sum = 0;
for(i=0;i<10000000;i++){
sum += i;
}
console.log(sum);
// alert不处理,JS执行和DOM渲染暂时卡顿
console.log(1)
alert('hello')
console.log(2)
避免DOM渲染冲突
- 浏览器需要渲染DOM
- JS可以修改DOM结构
- JS执行的时候,浏览器DOM渲染会暂停
- 两段JS也不能同时执行(防止DOM渲染冲突)
console.log(100)
setTimeout(function(){
console.log(200) //1000ms之后执行
},1000) //1000ms后再进入异步队列,先让其他JS代码执行
console.log(300)
console.log(400)
// 100 300 400 200
console.log(100)
$.ajax({
url:'xxxx',
sucess:function(result){ //ajax加载完才执行
console.log(result) //ajax后再进入异步队列,先让其他JS代码执行
}
})
console.log(300)
console.log(400)
// 100 300 400 result
异步存在的问题
- 没按照书写方式执行,可读性差
- callback中不容易模块化(callback就是异步后需要执行的函数)
事件轮询 event-loop
- 事件轮询是JS实现异步的具体解决方案
- 同步代码,直接执行
- 异步函数先放在异步队列中
- 待同步函数执行完毕,轮询执行异步队列
setTimeout(function(){
console.log(1)
},)
setTimeout(function(){
console.log(2)
},100)
console.log(3)
//主进程
console,log(3)
//异步队列
//立即被放入异步队列
function(){
console.log(1)
})
//100ms后这个函数会被放入异步队列
function(){
console.log(2)
})
jQuery-deferred
- jQuery1.5版本后引入jQuery-deferred
- 初步引入Promise的概念
//jQuery1.5之前
var ajax = $.ajax({
url:'data.json',
success:function(){
console.log('success1')
console.log('success2')
console.log('success3')
},
error:function(){
console.log('error')
}
})
console.log(ajax) // 返回一个XHR对象
//jQuery1.5之后
//1 .done.fail写法
var ajax = $.ajax('data,json')
ajax.done(function(){
console,log('success1')
}).fail(function(){
console,log('error')
}).done(function(){
console,log('success2')
})
console.log(ajax) // 返回一个deferred对象
//2 .then写法 很像Promise的写法
var ajax = $.ajax('data.json')
ajax.then(function(){
console.log('success1')
},function(){
console.log('error1')
}
.then(function(){
console.log('success2')
},function(){
console.log('error2')
}
jQuery1.5之后带来的变化
- 无法改变JS异步和单线程的本质
- 只能从写法上杜绝callback这种形式
- 它是一种语法糖形式,只是解耦了代码
- 很好的体现了开放封闭的原则
使用jQuery Deferred
//给出一段简单异步代码,使用setTimeout函数
var wait = function(){
var task = function(){
console.log('执行完成')
}
setTimeout(task,2000)
}
wait()
//使用jQuery Deferred
function waitHandle(){
var dtd = $.Deferred() //创建一个deferred对象
var wait = function(dtd){ //要求传入一个deferred对象
var task = function(){
console.log('执行完成')
dtd.resolve() //表示异步队列已经完成
//dtd.reject() //表示异步任务失败或者出错
}
setTimeout(task,1000)
return dtd //要求返回deferred对象
}
//注意这里一定要有返回值
return wait(dtd)
}
var w = waitHandle()
w.then(function(){
console.log('ok 1')
},function(){
console.log('err 1')
})
w.then(function(){
console.log('ok 2')
},function(){
console.log('err 2')
})
// 也可以使用 w.done w.fail
- deferred对象dtd的API分为两类
第一类:dtd.resolve dtd.reject (主动去执行异步函数结果 主动触发的)
第二类:dtd.then dtd.done dtd.fail(被动监听异步函数结果 被动监听的)
这两类不能混用,否则后果比较严重
使用dtd.promise()
function waitHandle(){
var dtd = $.Deferred()
var wait = function(dtd){
var task = function(){
console.log('执行完成')
dtd.resolve()
//dtd.reject()
}
setTimeout(task,1000)
return dtd.promise() //注意 这里返回promise而不是返回deferred对象
}
return wait(dtd)
}
var w = waitHandle() //经过上面的修改 这里的w是promise对象
$.when(w)
.then(function(){
console.log('ok 1')
})
.then(function(){
console.log('ok 2')
})
//注意 这里执行w.reject()会报错
//函数内部封装的时候可以使用resolve reject 函数外部使用时只能用then done fail被动监听了
总结
- jQuery1.5对ajax的改变
- 举例说明如何简单的封装和使用Deferred(强调开放封闭原则)
- Deferred和Promise的区别
Deferred有resolve、reject这种主动触发的函数,也有then、done、fail这种被动监听的函数,容易混用。
Promise只能使用then、done、fail被动监听,不能主动修改。
Promise
promise语法见之前的文章:https://www.jianshu.com/p/54c7c55677f7
function loadImg(src){
const promise = new Promise (function (resolve,reject){
var img = document.createElement('img')
img.onload = function(){
resolve(img)
}
img.onerror = function(){
reject()
}
img.src = src
})
return promise
}
var src = 'https://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src)
result.then(function(img){
console.log(img.width)
},function(){
console.log('failed')
})
result.then(function(img){
console.log(img.height)
},function(){
console.log('failed')
})
Promise异常捕获
catch不仅能捕获程序逻辑语法的报错,还能捕获reject()返回的报错
//规定then只接收一个成功的参数,最后统一用catch捕获异常
result.then(function (img){
console.log(img.width)
}).then(function (img){
console.log(img.height)
}).catch(function (ex){
console.log(ex) //统一用catch捕获异常
})
Promise多个串联
我们要求程序的图片按顺序加载,先加载图片1,再加载图片2
var src1 = 'https://www.baidu.com/static/img/index/logo1.png'
var result1 = loadImg(src1)
var src2 = 'https://www.baidu.com/static/img/index/logo2.png'
var result2 = loadImg(src2)
//链式操作
result1.then(function (img){
console.log('图片1加载完毕')
return result2 //一定要return result2!!! 非常重要!
}).then(function (img){
console.log('图片2加载完毕')
}).catch(function (ex){
console.log(ex) //统一用catch捕获异常
})
Promise.all和Promise.race
//Promise.all接收一个Promise对象的数组
//待全部完成之后,统一执行success
Promise.all([result1,result2]).then(datas=>{
//接收到的datas是一个数组,依次包含了多个promise返回的内容
console.log(datas[0])
console.log(datas[1])
})
//Promise.race接收一个Promise对象的数组
//只要有一个完成,就执行success
Promise.race([result1,result2]).then(datas=>{
//接收到的data即最先完成的promise的返回值
console.log(datas[data])
})
Promise状态
- 三种状态:pending、fulfilled、rejected
- 初始转态是pending
- pending变成fulfilled,或者pending变成rejected
- 转态不可逆
Promise的then
- Promise实例必须实现then这个方法
- then()必须接受两个函数作为参数
- then()返回的必须是一个Promise实例
async/await (ES7中的标准)
- then只是将callback函数拆分了
- async/await是最直接的同步写法
const load = async function(){
const result1 = await loadImg(src1)
console.log(result1)
const result2 = await loadImg(src2)
console.log(result2)
}
load()
用法
- 使用await,函数必须使用async标识
- await后面跟的是一个Promise实例
- 需要babel-polyfill
特点
- 使用了 Promise,并没有和Promise冲突
- 完全是同步的写法,再也没有回调函数
- 还是改变不了JS单线程、异步的本质