一、javascript的语言和执行环境是单线程的
浏览器js引擎中负责解析和执行js代码的线程只有一个,所以同一时间只能执行一个任务,所有任务都需要排队。
为什么会被设计为单线程?
(1)js设计之初,并不流行多进程多线程的语言,且当时硬件支持并不好(出生环境)
(2)其次因为多线程的复杂性,多线程操作需要枷锁,编码难度增加(使用难度,最初只用来完成简单的用户交互比如表单验证)
(3)js会操作dom,多线程同时操作同一dom会造成冲突。
二、同步与异步
js是单线程,所有任务都会排队,如果某个任务很耗时,则会堵塞其他排队的任务(扫二维出地铁站时,手机信号不好时,应该到旁边等二维码显示出来再出站 => 让能执行的任务先执行,不要一根筋卡在原地 =>网页请求多张图片时,不可能让图片排队加载,吃个饭回来页面可能都还没加载好)。所以js设计之初分为了同步和异步任务。
(1)同步任务,直接在主线程排队执行
(2)放在任务队列(event queue)中,等待同步任务执行完了再进入主线程执行。
常见使用异步任务的场景:
(1)网络请求:ajax请求、图片加载(任务可能会很耗时)
(2)setTimeout、setTimeInterval(本来就需要等待的任务)
(3)事件绑定:按钮绑定事件以后,用户可能点也可能不点(执行时间不确定)
(4)es6中的promise和es7的async与await
三、js事件循环机制
(1)同步任务直接进入主线程排队执行
(2)异步任务进入event table,等时间到了进入event queue排队。
setTimeout(()=>{},1000),要等1000毫秒后才能进入event queue排队
(3)等主线程的任务执行完以后,处于空闲状态,会去读取event queue的任务队列,如果有任务,则进入主线程执行。
注意点:
异步任务不会准时执行
(1)就算异步任务在两秒后进入event queue,也要等待主线程中完同步任务执行完后,再进入主线程执行。
(2)setTimeout和setInterval每次调用的时间间隔至少为4毫秒,这是因为函数嵌套(嵌套层级深)或setInterval回调函数阻塞的原因。
function test(){
console.time('test')
function test1(){
function test2(){
function test3(){
function test4(){
console.timeEnd('test')
}
test4()
}
test3()
}
test2()
}
test1()
}
test()
// => test: 0.04296875 ms
四、setTimeout和setInterval
setTimout接受连个参数,一个是回调函数,一个是进入event queue的时间。
setInterval也接受两个参数,一个回调函数,一个是向event queue中添加任务的间隔时间。
这两个定时器函数调用时会返回表示该超时排期的id,可用来清除这个超时任务。真正生产环境中,setInterval中每个回调函数执行时间不定所以可能会有阻塞,并不能保证时间间隔,最好不要用。
图片加载案例
function imgLoad(filePath,success, fail){
let img = new Image()
img.src = filePath;
img.onload = success(img)
img.onerror = fail(new Error('加载失败'))
}
imgLoad('XXX.png',
(img)=>{
let body = document.getElementByTagName('body')
body.appendChild(img)
},
(error)=>{
console.log(error)
}
)