setTimeout和setInterval以及同步和异步

一、setTimeout和setInterval
执行
setTimeout(fn,time)超时调用,超过时间time,就执行fn。
setInterval(fn,time)间歇调用,每调用周期时间time,就执行fn
销毁
两者调用后,都返回一个数值id,此id是计划执行代码的唯一标识符,可以通过他来取消尚未执行的调用。
clearTimeout(id)和clearInterval(id)
注意:取消间歇调用的重要性远远高于取消超时调用。因为在不干涉的情况下,间歇调用会一直执行到页面被销毁。

setTimeout的其他用法:

1、超时调用模拟间歇调用:(尽量使用超时调用)

var timer = setTimeout(function() { 
	console.log(123);
    timer = setTimeout(arguments.callee, 2000)//再次调用
  }, 2000) 

2、改变冒泡顺序:一般是先触发子元素再到父元素。现在要实现父元素先执行,那就用setTimeout延迟子元素若干秒后执行,也可以是0秒。

(function() { 
    setTimeout(function(){
    	console.log(222);
	},0)
	console.log(111);
}()) // 111 ; 222

3、异步

for(var i=0; i<5; i++){
	setTimeout(()=>{
		console.log(i);
	},1000);
};
console.log(i);
// 5 ;55555

使用闭包,使输出501234
方法一、

for(var i=0; i<5; i++){
	(function(j){
		setTimeout(()=>{
			console.log(j); // 0 1 2 3 4
		},1000);
	})(i);
};
console.log(i); // 5

方法二、

for(var i=0; i<5; i++){
	setTimeout(
	    (function(j){
		    return function(){
		        console.log(j); // 0 1 2 3 4 
		    };
		})(i),
	1000);
};
console.log(i); // 5

存在错误的写法:let在for循环外部是找不到i的

for(let i=0; i<5; i++){ //  let ,在for循环外部访问不到i
	setTimeout(()=>{
		console.log(i);
	},1000);
};
console.log(i); //  i is not defined

用ES6的方法改写,使输出012345
方法一、

for (var i = 0; i < 5; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(new Date, j); // 0 1 2 3 4
        }, 1000 * j);  // 这里修改 0~4 的定时器时间
    })(i);}
setTimeout(function() { // 这里增加定时器,超时设置为 5 秒
	console.log(new Date, i); // 5
}, 1000 * i);

方法二、

var p= Promise.resolve()
for (var i = 0; i < 5; i++) {
    (function(j){
       p=p.then(setTimeout(function() {//p=p.then()相当于实现链式操作
            console.log(new Date, j); // 0 1 2 3 4
        }, 1000*j ));  // 这里修改 0~4 的定时器时间
    })(i)
}
setTimeout(function(){
	p.then(console.log(new Date, i)); // 5
}, 1000*i) ;

方法三、

const tasks = [];for (var i = 0; i < 5; i++) {   // 这里 i 的声明不能改成 let,如果要改该怎么做?
    ((j) => {
        tasks.push(new Promise((resolve) => {
            setTimeout(() => {
                console.log(new Date, j);
                resolve();  // 这里一定要 resolve,否则代码不会按预期 work
            }, 1000 * j);   // 定时器的超时时间逐步增加
        }));
	})(i);
}

Promise.all(tasks).then(() => {
    setTimeout(() => {
        console.log(new Date, i);
}, 1000);   // 注意这里只需要把超时设置为 1 秒
});

举例:

console.log(1);
setTimeout(function() { console.log(2); },100)
setTimeout(function() { console.log(3); },50)
console.log(4); // 1 4 3 2

console.log(1);
setTimeout(function() { console.log(2); },0)
console.log(3); // 1 3 2 明明定时器时间为0 为什么不是打印 1 2 3 ?

因为浏览器(多线程)相关的线程有三个:
Js解析(单线程)-----js代码执行线程(主线程,同步任务)-----ui渲染线程-----事件循环线程(事件队列)

单线程:在引擎中负责解析和执行js代码的线程只有一个。js主要用于实现页面的交互效果,操作DOM,所以js只能是单线程,避免冲突。
其中单线程与ul渲染线程是互斥的,即运行主线程时ul渲染会停止工作,为了防止js的DOM操作导致两线程冲突
事件队列执行过程:
在这里插入图片描述
setTimeout与setInterval的执行会等到主线程的所有任务全部执行后,才会再执行其中的回调函数,所以都是异步,但又因为它必须是等到主线程全部执行完才会执行,所以可以称之为伪异步。

二、同步和异步

什么是同步异步?

  1. 同步:顺序执行,始终和前文保持在一个上下文。
    优点:自上而下顺序执行下来,对结果的处理比较简单,就近处理。可以容易捕获、处理异常。
    缺点:当代码量大时,自上而下执行解析时间过长,容易阻塞,耗费时间执行效率低,影响用户体验
    使用场景:不使用异步
  2. 异步:由浏览器(多线程)提供另外的线程去操作,始终等待全部的同步任务执行完毕才回到js线程执行。
    优点:避免阻塞,提高执行效率。可以立即或者延时给调用者返回结果,也可以多次调用后统一返回一个结果集合。
    缺点:占用更多资源,不利于对进程控制。
    使用场景:不涉及共享资源时,没有时序要求时,耗时操作,提高用户体验和使用性能时,不影响主线程的逻辑时。
    常见异步任务:回调函数(计时器、事件监听、ajax),发布/订阅,promise,async/await

AJAX 异步的原理
发送一个ajax请求,浏览器会有一个专门的线程来执行该任务。因为ajax的异步请求也是有成功的回调、失败的回调,所以这些回调就会和定时器的回调一样被放到等待队列中。
浏览器会再次提供一个线程来接收ajax请求返回的数据。等到主线程的同步任务执行完毕之后,事件循环队列就会遍历事件队列,将回调任务交回给主线程。

为什么会有异步?

  1. 若JS没有异步,只能自上而下执行,万一上一行解析时间很长,那么下面的代码就会被阻塞。进度‘卡死’,影响用户体验。
  2. 同步可以保证顺序一致,但是容易导致阻塞;
  3. 异步可以解决阻塞问题,但是会改变顺序性,根据不同的需要去写你的代码

JavaScript同步一定比异步回调任务优先执行。这种机制就是为了防止由于某个任务占时太多而阻塞js单线程的情况发生。

我们可以把js代码看做两部分。一部分是必须要用js引擎内的单线程来执行的。另一部分是Web APIs。由于Web APIs是由浏览器(浏览器内核是多线程)提供的,因此这些任务可以不占用js的线程,用引擎环境之外的浏览器线程来跑。这给js提供了在单位时间内执行多个任务的可能性。

所以严格来讲像AJAX,setTimeout()这些异步Web API任务是可以和js的同步任务同时执行的。但是异步回调任务一定是在js同步任务完成后执行的,因为异步回调任务一定是在js线程中完成的。

js引擎内有一个很重要的部分叫call stack。call stack里装的是所有要执行的任务。你可以想象成js执行单元就只有一个使命:完成call stack最顶上的那个任务,也就是当前任务。执行完当前任务后就把它甩出去,继续跑下一个置顶的任务,直到call stack里的任务全部跑完。这个时候执行单元会对event loop说:“喂,我这儿任务都跑完了,你去看看callback queue还有没有闲置的任务。有的话给我送过来”。而callback queue里放的就是待执行的异步回调任务。
简而言之,只有等call stack空了,才轮到回调任务被推进任务堆栈里执行。

所以这就解释了为什么在js里执行异步操作的时候,为了保证数据更新的准确性,一定要用到回调函数,或者promise。这些机制都保证了只有当异步操作真正完成过后,函数才会被推进callback queue里等待call stack对它的执行,从而返回正确的数值结果。即
异步操作 在浏览器线程
异步执行 回到js单线程

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值