一、单线程
单线程:在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。
为什么JS采用单线程?
- 浏览器需要渲染DOM
- JS可以修改DOM
- JS执行的时候浏览器DOM渲染停止
- 两段JS不能同时执行(同时渲染DOM就冲突了)
- webworker支持多线程,但是不能访问DOM
为了避免渲染DOM冲突,JS必须是单线程的,而且和浏览器渲染共用一个线程。
二、异步
单线程的解决方案就是异步。
异步有哪些问题?
- 问题一、没按照书写的顺序执行,可读性差。
- 问题二、callback中不容易模块化。
三、event-loop
人们把javascript调控同步和异步任务的机制称为事件循环(event-loop)
event-loop机制如下:
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
- 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件,哪些对应的异步任务,是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步
setTimeout(function () {
console.log(1)
},1000)
setTimeout(function(){
console.log(2)
})
console.log(3)
//3 2 2
四、jQuery的Deferred
1、jquery 1.5之前Ajax的写法
var ajax = $.ajax({
utl:'data.json',
success:function(){
console.log('success1')
console.log('success2')
console.log('success3')
},
error:function(){
console.log('error')
}
})
console.log(ajax) //返回一个XHR对象
2、jquery 1.5之后Ajax的写法
第一种写法
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对象
第二种写法
var ajax = $.ajax('data.json')
ajax.then(function(){
console.log('success1')
},function(){
console.log('error1')
}).then(function(){
console.log('success2')
},function(){
console.log('success2')
})
这种写法很像Promise的写法
3、使用jquery Deferred
function waitHandle(){
var dtd = $.Deferred()
var wait = function(dtd){
var task = function (){
console.log('执行完成')
//执行成功
dtd.resolve()
//执行失败
// dtd.reject()
}
setTimeout(task,2000)
return dtd
}
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.then(function (){
console.log('OK 3')
},function (){
console.log('err 3')
})
//开放封闭原则:对扩展开放,对修改封闭
- 总结,dtd的api可分为两类,用意不同。
- 第一类:dtd.resolve dtd.reject
- 第二类:dtd.then dtd.done dtd.fail
- 这两类应该分开,否则后果很严重!
- 可以在上面代码最后执行dtd.reject()试一下后果
使用dtd.promise()
function waitHandle(){
var dtd = $.Deferred()
var wait = function(dtd){
var task = function (){
console.log('执行完成')
//执行成功
dtd.resolve()
//执行失败
// dtd.reject()
}
setTimeout(task,2000)
return dtd.promise()
}
return wait(dtd)
}
var w = waitHandle() //promise对象
$.when(w).then(function (){
console.log('OK 1')
},function (){
console.log('err 1')
})
$.when(w).then(function (){
console.log('OK 2')
},function (){
console.log('err 2')
})
$.when(w).then(function (){
console.log('OK 3')
},function (){
console.log('err 3')
})
//开放封闭原则:对扩展开放,对修改封闭
五、Promise的基本使用和原理
function loadImg(src) {
var promise = new Promise(function(resolve, reject){
var img = document.createElement('img')
//throw new Error('自定义错误')
img.onload = function(){
resolve(img)
}
img.onerror = function(){
reject('图片加载失败!')
}
img.src = src
})
return promise
}
var src = "https://img10.360buyimg.com/n1/jfs/t3847/338/2200026645/140646/c239125d/58537d88Nc15f021d.jpg"
var result = loadImg(src)
result.then(function(img){
console.log(1,img.width)
return img
}).then(function (img) {
console.log(2,img.height)
}).catch(function (ex){
console.log(ex)
})
多个串联
Promise.all()等所有都执行完后,执行then中内容。
Promise.race()有一个执行完就执行then中内容。
function loadImg(src) {
var promise = new Promise(function(resolve, reject){
var img = document.createElement('img')
//throw new Error('自定义错误')
img.onload = function(){
resolve(img)
}
img.onerror = function(){
reject('图片加载失败!')
}
img.src = src
})
return promise
}
var src2 = 'https://img11.360buyimg.com/n1/s450x450_jfs/t3427/221/1828863550/93027/757e93ac/5832861fN2566f854.jpg'
var result1 = loadImg(src2)
var src1 = 'https://img10.360buyimg.com/mobilecms/s250x250_jfs/t22588/177/2455233485/268618/d7383c4f/5b83afceNdbaff291.jpg'
var result2 = loadImg(src1)
Promise.all([result2,result1]).then(function (datas){
console.log('all',datas[0])
console.log('all',datas[1])
})
Promise.race([result1,result2]).then(function (data){
console.log('race',data)
})
Promise的标准-状态变化
- 三种状态:pending fulfilled rejected
- 初始状态是pending
- pending可变为fulfilled或rejected
- 状态变化不可逆
Promise的标准-then
- Promise实例必须实现then这个方法
- then必须接收两个或一个函数作为参数
- then返回的必须是一个Promise实例(如果没有明确返回,那么它返回的是本身)
Promise的原理
Promise其实内部也有一个defers队列存放事件,.then的事件就在里面,程序开始执行的时候,.then就已经放入下一个事件,然后后面当异步操作完成时,resolve触发事件队列中的事件,便完成了一个.then操作, 其实到这里我们就可以很快地想出一种解决方案,每次异步操作完成通过resolve触发事件并将事件从事件队列中移除,通过事件队列中的事件的resolve使事件的触发持续下去。
我们可以用十几行代码就可以实现这样的逻辑,实现一个简单的异步编程方案
function P(fn) {
var value = null;
var events = [];
this.then = function(f) {
events.push(f);
return this;
}
function resolve(newValue) {
var f = events.shift();
f(newValue, resolve);
}
fn(resolve);
}
function a() {
return new P(function(resolve) {
console.log("get...");
setTimeout(function() {
console.log("get 1");
resolve(1);
}, 1000)
});
}
a().then(function(value, resolve) {
console.log("get...");
setTimeout(function() {
console.log("get 2");
resolve(2);
}, 1000)
}).then(function(value, resolve) {
console.log(value)
})
控制台得到如下结果:
get...
get 1
get...
get 2
2
六、async/await
- then只是将callback拆分了
- async/await是最直接的同步写法
async/await的用法
- 使用await,函数必须有async标识
- await后跟的是一个Promise实例
- 需要babel-polyfill
import 'babel-polyfill'
function loadImg(src) {
var promise = new Promise(function(resolve, reject){
var img = document.createElement('img')
//throw new Error('自定义错误')
img.onload = function(){
resolve(img)
}
img.onerror = function(){
reject('图片加载失败!')
}
img.src = src
})
return promise
}
var src1 = 'https://img11.360buyimg.com/n1/s450x450_jfs/t3427/221/1828863550/93027/757e93ac/5832861fN2566f854.jpg'
var src2 = 'https://img10.360buyimg.com/mobilecms/s250x250_jfs/t22588/177/2455233485/268618/d7383c4f/5b83afceNdbaff291.jpg'
const load = async function(){
const result1 = await loadImg(src2)
console.log(result1)
const result2 = await loadImg(src1)
console.log(result2)
}
load()
七、jQuery的Deferred、Promise、async/await总结
- 无法改变JS异步和单线程的本质
- 只能从写法上杜绝callback这种形式
- 它是一种语法糖,但是解耦了代码
- 很好的体现了:开放封闭原则