并行和并发的区别
并发是一个宏观概念,是指在一段时间内,A,B 两个任务通过切换,完成了,这种叫做并发
并行是一个微观的概念,指 CPU 存在两个核心,同时完成了 A,B 两个任务,称为并行
回调函数,回调地狱
回调地狱这个词语上面,他的重点在于‘回调’,而回调在 JS 中应用最多的场景当然是异步编程了
‘回调地狱’所说的嵌套其实是指异步的嵌套
带来了两个问题可读性的问题和信任问题
可读性问题
我们在写业务逻辑的时候,一般是下面这样的
listen( "click", function handler(evt){
doSomething1();
doSomething2();
doSomething3();
doSomething4();
setTimeout( function request(){
doSomething8();
doSomething9();
doSomething10();
ajax( "http:// some. url. 1", function response( text){
if (text == "hello") {
handler();
} else if (text == "world") {
request();
}
});
doSomething11();
doSomething12();
doSomething13();
}, 500);
doSomething5();
doSomething6();
doSomething7();
});
这些都 Something,有些是同步的,有些是异步的,这会读起来十分吃力,因为要记住他们的执行顺序
这就是异步的嵌套带来的可读性的问题,它是由异步的运行机制引起来的
信任问题
这里主要讨论异步回调
可能出现的问题:
- 回调过早
- 回调过晚或没有回调
- 回调多次
- 等等 问题的根源在于控制反转,在面向对象的应用中是依赖注入,实现了模块间的解藕;
而在回调中,并不友好,控制权被交给了第三方,由第三方来决定什么时候调用回调,以及如何调用回调
promise 怎么解决这两个问题
解决可读性的问题
Promise 相当于给了你一张可以把解题思路记录下来的草稿纸,不需要用脑子记录执行顺序
解决信任问题
Promise 并没有取消控制反转,只是反转了控制反转
这种方式有点像事件的触发,它与普通的回调的方式的区别在于:
普通的方式,回调成功后的操作直接写在了回调函数里面,而这些操作的调用由第三方控制;
在 Promise 中,回调只负责成功后的通知,而回调成功后的操作放在了 then 的回调里面,由 Promise 精准控制
- 回调过早的问题
由于 Promise 是异步的,所以不会出现异步的同步调用,即使是在决议之前的错误,也是异步的,不会产生同步(调用过早)的困扰
var a = new Promise((resolve, reject) => {
var b = 1 + c; // ReferenceError: c is not defined,错误会在下面的a打印出来之后报出。
resolve(true);
})
console.log(1, a);
a.then(res => {
console.log(2, res);
})
.catch(err => {
console.log(err);
})
- 回调过晚,没有回调的问题
Promise 并不会回调过晚,只要决议了,它就会按照规定运行。对于网络或者服务器的问题,并不是 Promise 能解决的,一般这种情况会使用 Promise 的竞态 API Promise.race 加一个超时时间
function timeoutPromise(delay) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject("Timeout!");
}, delay);
});
}
Promise.race([doSomething(), timeoutPromise(3000)])
.then(...)
.catch(...);
- 对于回调次数太少或者太多的问题
由于 Promise 只能被决议一次,且决议之后无法改变,所以即使是多次回调,也不会影响结果,决议之后的调用都会被忽略
常用定时器函数
常见定时器函数: setTimeout,setInterval,requestAnimationFrame
setTime 和 setInterval 的延时最小间隔是 4ms(W3C 在 HTML 标准中规定);在 JS 中没有任何代码是立即执行的,但一旦进程空闲就尽快执行。这意味着无论是 setTimeout 还是 setInterval,所设置的时间都只是 n 毫秒被添加到队列中,而不是过 n 毫秒后立即执行
setInterval
每隔一段时间,执行一次回调函数
缺点:
setInterval 每隔 100ms 向队列中添加一个事件,100ms 后,添加 T1 定时器代码到队列中,主线程中还有任务在执行,所以等待,some exent 执行结束后执行 T1 定时器,又过了 100ms,T2 定时器被添加到队列中,主线程还在执行 T1,所以等待;又过了 100ms,理论又要王队列里推一个定时器代码,但由于此时 T2 还在队列中,T3 不会被添加,就过就是被跳过,而且执行完 T1,马上执行 T2
- 和 setTimeout 一样,不能保证预期的时间执行任务
- 某些间隔可能会被跳过
- 可能多个定时器连续执行,并没有达到定时器的效果
function demo() {
setInterval(function(){
console.log(2)
},1000)
sleep(2000)
}
demo()
以上代码,如果定时器在执行过程中出现了耗时操作,多个会滴哦啊函数会在耗时操作结束以后同时执行,这就会带来性能上的问题
setTimeout
错误观点:setTime 是延时多久,就多久之后执行,但其实此观点是错误的
因为 JS 是单线程的,如果前面的代码影响了性能,会导致 setTimeout 不会按期执行,当然,我们可以通过代码去修正 setTimeout,从而使定时器相对准确
let period = 60 * 1000 * 60 * 2; //两个小时
let startTime = new Date().getTime();
let count = 0;
let end = new Date().getTime() + period;
let interval = 1000;
let currentInterval = interval; // currentInterval和interval都是一秒
function loop() {
count++;
let offset = new Date().getTime() - (startTime + count * interval);
let go = function () {
currentInterval = interval - offset;
console.log('代码执行时间:' + offset, '下次循环间隔' + currentInterval);
setTimeout(loop, currentInterval);
}
setTimeout (go(), 400);
// 得到下一次循环所消耗的时间
}
setTimeout(loop, currentInterval);
但是与 setInterval 相比好处
- 在前一个定时器执行完前,不会向队列插入新的定时器(解决缺点 1)
- 保定定时器间隔(解决问题 2)
requestAnimationFram
requestAnimationFram 解决了浏览器不知道 javaScript 动画什么时候开始,不知道最佳循环间隔的问题
requestAnimationFrame 的速度是由浏览器决定的,不同浏览器会自行决定最佳的帧效率。
- 优点
- setAnimationFram 自带函数节流功能,基本可以保证 16.6 毫秒只执行一次(不掉帧的情况下)
- 并且该函数的延时效果是准确的,没有其他定时器时间不准的问题
- 基础
- 接受一个回调函数作为参数
window.requestAnimationFrame(callback)
- callBack 执行时,它的参数就是系统传入的一个高进度时间戳,单位是毫秒,表示距离网页加载的时间
- window.requestAnimationFram()返回的是一个整数,这个整数可以传入 window.requestAnimationFram(),用来消除回调函数的执行