JavaScript是单线程的,很多任务都是堆到这个线程上的,然后一个一个的解决。
这就导致了一些问题,比如当前面有一个函数在做复杂运算时,没有将控制权返回浏览器,那么此时你无法在做其他的事,这叫阻塞。下面是官方给出的一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <div class='wrapper'> <button class='btn'>Click</button> </div> <script> const wp = document.querySelector('.wrapper'); const btn = document.querySelector('.btn'); btn.addEventListener('click', () => { let myDate; for(let i = 0; i < 10000000; i++) { let date = new Date(); myDate = date; } console.log(myDate); let pElem = document.createElement('p'); pElem.textContent = 'This is a newly-added paragraph.'; wp.appendChild(pElem); }) </script>
|
可以看到点击click后,先做复杂运算,过段时间才会显示段落p(这两个没有逻辑关系)
这表明JS是一种同步的、阻塞的、单线程的语言,一次只能执行一个操作。但web浏览器定义了函数和API,允许我们当某些事件发生时不是按照同步方式,而是异步地调用函数(比如,时间的推移,用户通过鼠标的交互,或者获取网络数据)
在网络上,异步更为重要,如下面这个例子
1 2
| var response = fetch('myImage.png'); var blob = response.blob();
|
因为不知道第一步下载图片的时间,所以第二步有可能会出错。需要等第一步返回才能进行第二步
有两种解决方案,以前的回调callback,和现在的promise,重点学习promise
不过在此之前要学习几个函数方便对异步的理解,这些函数设置的异步代码实际上在主线程上运行,但是您可以在迭代之间运行其他代码,运行效率或高或低,这取决于这些操作的处理器密集程度。
异步函数
setTimeout()
可以指定时间后运行代码
- 第一个参数是函数引用
- 第二个参数是时间间隔,单位毫秒
- 后面是可变参数,这些参数会传递给第一个参数的函数
返回一个标志,后续可根据这个标志使用clearTimeout
取消这个任务,如
1 2
| let taskFlag = setTime(console.log, 2000, 'I love you'); clearTimeout(taskFlag);
|
当时间间隔设置为0时,会在主线程执行后立刻执行
1 2 3 4
| setTimeout(function() { alert('World'); }, 0); alert('Hello');
|
setInterval()
与setTimeout
的只执行一次不同,setInterval
会循环执行,它的参数与setTimeout
一致
一个数字时钟的例子
1 2 3 4 5 6 7 8 9 10 11
| <div> <p id='demo'></p> </div> <script> function displayTime() { let date = new Date(); let time = date.toLocaleTimeString(); document.getElementById('demo').textContent = time; } const createClock = setInterval(displayTime, 1000); </script>
|
它同样会返回一个标志,使用clearInterval可以停止执行
下面是一个秒表例子
源码(最开始使用Date来计时,但是由于暂停逻辑有点麻烦,就采用一个变量记录秒方便一点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <div> <p id="clock"></p> <button id='stop'>stop</button> <button id='start'>start</button> <button id='reset'>reset</button> </div> <script> const stop = document.querySelector('#stop'); const start = document.querySelector('#start'); const reset = document.querySelector('#reset');
let sec=0, s=0, m=0, h=0; let stopFlag;
displayCount();
start.onclick = () => { stopFlag = setInterval(displayCount, 1000); start.disabled = true; }; stop.onclick = () => { clearInterval(stopFlag); start.disabled = false; }; reset.onclick = () => { clearInterval(stopFlag); sec = 0; start.disabled = false; displayCount(); }
function displayCount() { time(); if(s<10) s = '0'+s; if(m<10) m = '0'+m; if(h<10) h = '0'+h; timeText = h+':'+m+':'+s; document.getElementById('clock').textContent = timeText; } function time() { if(sec<3600){ h = 0; m = parseInt(sec / 60); s = sec % 60; } else { h = sec % 3600; m = parseInt(sec / 60) - h; s = sec % 60; } sec++; } </script>
|
递归setTimeout()
递归的作用可以实现与setInterval
一样的功能
1 2 3 4 5 6 7
| let i = 0; funtion run(){ cosole.log(i); i++; setTimeout(run,100); } setTimeout(run,100);
|
与setInterval
的区别
- setTimeout的时间间隔是固定的
- setInterval的事件间隔包括了程序运行的时间
- 所以当代码花的时间比设置的间隔更长时建议使用递归
回调
这是一个比较老套的做法
比如addEventListener()
的第二个参数就是异步,第一个参数监听事件,第二个参数就是当事件发生时调用的函数。第二个参数相当于只是传递了过去,并没有执行。
下面是官方的一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function loadAsset(url, type, callback) { let xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.responseType = type;
xhr.onload = function() { callback(xhr.response); };
xhr.send(); }
function displayImage(blob) { let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img'); image.src = objectURL; document.body.appendChild(image); }
loadAsset('https://cdn.jsdelivr.net/gh/yangchaohe/yangchaohe.github.io@static/img/blog/mito2.jpg', 'blob', displayImage);
|
当获取图片后对图片做一些处理
回调函数不一定都是异步的,如forEach
Promise
简介
现代异步编程的常用方案,比回调、事件更加合理、强大。这里面很多东西比较晦涩难懂,我尽量梳理清楚
为了方便理解Promise,需要理清几个概念
1.事件队列
像promise这样的异步操作被放入事件队列中,事件队列在主线程完成处理后运行,这样它们就不会阻止后续JavaScript代码的运行。排队操作将尽快完成,然后将结果返回到JavaScript环境。
2.回调和Promise
使用监听鼠标事件,当鼠标点击就会执行事件(函数),也就是说这个事件被阻塞了,只有给定条件发生才会执行;同样Promise也是将里面的代码阻塞,然后监听的是资源是否获取到了,进而执行代码.
3.promise里的状态
每个promise对象都存在一个状态,初始状态叫pending
,成功状态fullfilled
,失败状态rejected
,当从初始状态转化成失败或者成功时,就无法再改变状态
参考文献