JS定时器
首先了解什么是同步代码?什么是异步代码?同步代码和异步代码的关系是什么呢?
1、同步代码:
代码单线执行,发送服务器请求后,等待返回数据,会出现网页空白(阻塞网页运行)(通俗讲,就是代码按照顺序执行,但凡上面代码没有执行完,下边代码不能执行)
2、异步代码:
代码发送请求后继续执行后续代码,不等待服务器返回数据(网页运行流畅)
js中常见的异步执行代码:
1.ajax请求:异步 JavaScript 和 XML
2.定时器:间隔一段时间才会执行,
3.事件处理函数:满足事件触发条件才会执行
异步底层原理:
js代码是单线执行,代码从上往下依次执行,当遇到【异步任务】时,将其储存到一个【执行队列】中,当主线的代码执行结束后,在根据服务器的处理先后顺序执行剩余的【异步任务】
2、同步代码和异步代码的关系
1、JS中执行永远是同步代码执行完,异步代码才会执行
2、异步代码存放的位置是:在栈中,堪称两个队列
3、异步代码的特点:①完全不阻塞页面的渲染和js的代码的执行
3、面试题目alert是否会打印?
<script type="text/javascript">
var t=true;
window.setTimeout(function(){
t=false;
},1000);
while(t){
}
alert('end');
</script>
首先答案:不会打印
这段代码,setTimeout是异步线程,需要等待js引擎处理完同步代码(while语句)之后才会执行,while语句直接是个死循环,js引擎没有空闲,不会执行下面的alert,也不会插入setTimeout。所以即使这个时候你把setTimeout的时间改成0,他还是不会执行。
JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序
一、浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程。
javascript引擎是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。
GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。(当线程中没有执行任何同步代码的前提下才会执行异步代码)
4、js 定时器有以下两个方法和取消定时器的两个方法:
4.1、创建定时器
(1)setTimeout
第一个参数:需要等待执行的内容,回调函数
第二个参数:等待时间以ms为单位(1000ms=1s)
返回值:定时器的编号
(2)setInterval()
第一个参数:需要等待执行的内容,回调函数
第二个参数:间隔时间(1000ms=1s),每隔间隔时间执行一次(同步代码执行完之后)
返回值:
4.2、取消/清除定时器
(1)clearTimeout()//清除setTimeout定时器
参数;①接收定时器的编号
(2)clearInterval()//清除setInterval定时器
参数;①接收定时器的编号②定时器的结束时间/定时器的执行时长
<body>
<button id="btn1">Btn1</button>
<button id="btn2">Btn2</button>
<script>
let timer = null
let btn1 = document.querySelector("#btn1")
let btn2 = document.querySelector("#btn2")
btn1.onclick = function (){
timer = setTimeout(function (){
console.log("fasfasf")
},5000)
}
btn2.onclick = function (){
clearTimeout(timer)
}
</script>
</body>
规定每一秒进行打印一个i++,直到第十秒停止用setInterval
// 方法一:每执行一次定时器内部都进行判断,当10s的时候进行清楚
let i = 0
let timer = setInterval(function (){
console.log(i++)
if(i>10){
clearInterval(timer)
}
},1000)
//方法二:直接设置一个setInterval定时器,然后设置一个清除定时器,10s后清除
let j=0
let timer2 = setInterval(function (){
console.log(j++)
})
4.3、区别:
setTimeout只在指定时间后执行一次,代码如下:
<script>
//定时器 异步运行
function hello(){
alert("hello");
}
//使用方法名字执行方法
let t1 = window.setTimeout(hello,1000);
let t2 = window.setTimeout("hello()",3000);//使用字符串执行方法
let t2 = window.setTimeout(function(){
alert("hellopppp");
},3000);//直接写入回调函数
window.clearTimeout(t1);//去掉定时器
</script>
setInterval以指定时间为周期循环执行,代码如下:
function refreshQuery(){
$("#mainTable").datagrid('reload',null);
}
//实时刷新时间单位为毫秒
setInterval('refreshQuery()',8000); //每隔8秒刷新一次,停不下来
/* 刷新查询 */
5、Promise异步变同步
5.1Promise的出现,原本是为了解决回调地狱的问题
5.2地狱函数:
在使用JavaScript时,为了实现某些逻辑经常会写出层层嵌套的回调函数,如果嵌套过多,会极大影响代码可读性和逻辑,这种情况也被成为回调地狱。比如说你要把一个函数 A 作为回调函数,但是该函数又接受一个函数 B 作为参数,甚至 B 还接受 C 作为参数使用,就这样层层嵌套,人称之为回调地狱,代码阅读性非常差。比如:
var sayhello = function (name, callback) {
setTimeout(function () {
console.log(name);
callback();
}, 1000);
}
sayhello("first", function () {
sayhello("second", function () {
sayhello("third", function () {
console.log("end");
});
});
});
//输出: first second third end
5.3、解决回调地狱
解决回调地狱有很多方法,比如:Promise 对象、Generator 函数、async 函数
5.4、Promise 对象解决回调地狱
采用链式的 then,可以指定一组按照次序调用的回调函数。这时,前一个 then 里的一个回调函数,返回的可能还是一个 Promise对象(即有异步操作),这时后一个回调函数,就会等待该 Promise对象的状态发生变化,才
会被调用。由此实现异步操作按照次序执行。
var sayhello = function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name);
resolve(); //在异步操作执行完后执行 resolve() 函数
}, 1000);
});
}
sayhello("first").then(function () {
return sayhello("second"); //仍然返回一个 Promise 对象
}).then(function () {
return sayhello("third");
}).then(function () {
console.log('end');
}).catch(function (err) {
console.log(err);
})
//输出:first second third end
上面代码中,第一个 then 方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就继续执行第二个 then 里的回调函数
5.5、Promise的特点:
resolve表示代码执行成功,reject表示代码执行失败。Promise是一个构造函数,构造函数创建对象的时候参数是一个回调函数,里边的参数是resolve和reject,这两个都是函数。
1、接收一个回调函数(参数)
2、回调函数是第一段异步操作
3、回调函数有resolve和reject两个函数形参
4、resolve,意味着紧挨着的then执行
5、reject,意味着.catch执行
5.6、怎么样进行顺序执行多个异步任务?
1.直接then链式调用 不多说
2.利用Promise.resolve()+forEach循环/reduce循环,将promise串成一任务队列,本质上是简化的链式调用
let a = function() {
return new Promise((resolve, reject) => {
setTimeout(()=>{
console.log('11111111111',)
resolve('第一个异步进程')
},3000)
})
}
let b = function(){
return new Promise((resolve, reject) => {
setTimeout(()=>{
console.log('2222222222' )
resolve('第二个异步进程')
},2000)
})
}
let c = function(){
return new Promise((resolve, reject) => {
setTimeout(()=>{
console.log('3333333333333')
resolve('第三个异步进程')
},1000)
})
}
reduce构建异步队列
let promiseArr = [a, b, c]
promiseArr.reduce((prev, cur)=>{
return prev.then(() => {
return cur().then(res => {
console.log( res)
})
})
}, Promise.resolve())
本质上相当于
Promise.resolve().then(res => {
return new Promise((resolve, reject) => {
setTimeout(()=>{
console.log('11111111111', )
resolve('第一个异步进程')
},3000)
})
// 只有当第一个then返回了promise对象才会接着调用下一个的then方法
}).then(() => {
return new Promise((resolve, reject) => {
setTimeout(()=>{
console.log('2222222222', )
resolve('第二个异步进程')
},2000)
})
}).then(() => {
return new Promise((resolve, reject) => {
setTimeout(()=>{
console.log('33333333', )
resolve('第三个异步进程')
},1000)
})
})
forEach构建异步队列
function fun(arr){
let res = Promise.resolve()
arr.forEach(item => {
res = res.then(item)
})
}
fun([a,b,c]).then( res => {
console.log(res)
})
//相当于
Promise.resolve().then(
a
).then(
b
).then(
c
)
3.async await实现
let arr = [a, b, c]
async function fun(arr){
for(const item of arr){
const res = await item().then()
console.log(res)
}
}
fun(arr)
案例
//===========做饭==============
function cook(){
console.log('开始做饭。');
var p = new Promise(function(resolve, reject){ //做一些异步操作
setTimeout(function(){
console.log('做饭完毕!');
resolve('鸡蛋炒饭');
}, 1000);
});
return p;
}
//===========吃饭=============================
function eat(data){
console.log('开始吃饭:' + data);
var p = new Promise(function(resolve, reject){ //做一些异步操作
setTimeout(function(){
console.log('吃饭完毕!');
resolve('一块碗和一双筷子');
}, 2000);
});
return p;
}
cook()
.then(function(data){
//===============通过抛出异常的方式将cook的状态设置为reject================
throw new Error('米饭被打翻了!');
eat(data);
})
.catch(function(data){
//=============捕获reject状态后的异常信息==============
console.log(data);
});