异步
先看一下下面对async、defer的介绍
下面的内容是文章《异步vs延迟属性》里的内容
<script>
我们首先定义什么<script>没有任何属性。HTML文件将被解析,直到脚本文件被命中,在这一点解析将停止,并将请求获取文件(如果它是外部的)。然后在执行解析之前执行该脚本。
image.png<script async>
async 在HTML解析过程中下载文件,并在完成下载后暂停HTML解析器执行。
image.png<script defer>
defer在HTML解析过程中下载文件,只有在解析器完成后才能执行该文件。defer脚本也可以按照它们在文档中出现的顺序执行。
image.png什么时候应该用什么?
通常你想async在可能的地方使用,defer然后没有属性。以下是一些一般规则:
- 如果脚本是模块化的,并且不依赖于任何脚本然后使用async。
- 如果脚本依赖或被另一个脚本依赖,那么使用defer。
- 如果脚本很小并被脚本依赖,async那么请使用内置的脚本之上的script任何属性。
async支持
IE9及以下版本的执行有一些非常糟糕的错误,defer以致执行顺序不符合要求。如果您需要支持<= IE9,我建议不要使用defer,如果执行顺序很重要,请将脚本包含在没有属性的位置。
单线程
因为js运行在浏览器中,是单线程的,每个window一个JS线程
既然是单线程的,在某个特定的时刻只有特定的代码能够被执行。
而浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,会创建事件并放入执行队列中。
javascript引擎是单线程处理它的任务队列,你可以理解成就是普通函数和回调函数构成的队列。
当异步事件发生时,如鼠标点击事件发生、定时器触发事件发生、XMLHttpRequest完成回调触发等,将他们放入执行队列,等待当前代码执行完成。
异步事件驱动
前面已经提到浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,
例如:鼠标点击事件、窗口大小拖拉事件、定时器触发事件XMLHttpRequest完成回调等。
当一个异步事件发生的时候,它就进入事件队列。浏览器有一个内部大消息循环,Event Loop(事件循环),会轮询大的事件队列并处理事件。
例如,浏览器当前正在忙于处理onclick事件,这时另外一个事件发生了(如:window onSize),这个异步事件就被放入事件队列等待处理,只有前面的处理完毕了,空闲了才会执行这个事件。
setTimeout也是一样,当调用的时候,js引擎会启动定时器timer,大约xxms以后执行xxx,
当定时器时间到,就把该事件放到主事件队列等待处理(浏览器不忙的时候才会真正执行)
异步示例——事件
var items
var i
document.body.innerHTML = `
<ol>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ol>
`
items = document.querySelectorAll('li')
for(i=0;i<items.length;i++){
items[i].onclick = function(){
console.log(i)
}
}
image.png
函数解析后,不会马上执行
等鼠标点击有序列表项,
才会执行,
无论是哪一项都会在控制台打出‘5’。
因为绑定事件的处理函数,
等输入流给出事件触发信号,
才会执行函数。
但是,
其它的代码会继续解析并执行。
所以,
变量i的值继续增在,增加到5,
这时候事件还没触发,所以函数也不会执行,
等它执行时,
获取的变量i已经是‘5’了
异步示例——定时器
var a = 1
setTimeout(function(){
var a = 2
console.log(a)
},1000)
console.log(a)
1
undefined
//一秒之后才打出‘2’
2
上面,先打出1
说明,全局环境里console.log(a)
这一行代码会先执行,
然后再打出‘2’,
说明,setTimeout()
里的console.log(a)
,
虽然先被浏览器解析,
但是要等1秒之后,IO输入流才给它一个信号说一秒时间到了,
才会执行了
异步示例——请求
data.json
{"name":"llz"}
javascript
var a = 1
var xhr = new XMLHttpRequest()
xhr.open('GET','/data.json')
xhr.onload = function(){
console.log(xhr.responseText)
}
xhr.send()
console.log(1)
console
1
{"name":"llz"}
控制台,会先打出1,
等请求的数据下载完后,
才会打出请求的数据‘{"name":"llz"}’
说明请求也是异步
非阻塞js的实现(non-blocking javascript)
js在浏览器中需要被下载、解释并执行这三步。在html body标签中的script都是阻塞的。
也就是说,顺序下载、解释、执行。尽管Chrome可以实现多线程并行下载外部资源,
例如:script file、image、frame等(css比较复杂,在IE中不阻塞下载,但Firefox阻塞下载)。
但是,由于js是单线程的,所以尽管浏览器可以并发加快js的下载,但必须依次执行。
所以chrome中image图片资源是可以并发下载的,但外部js文件并发下载没有多大意义。
回调
回调 == 异步 + 函数调用
回调示例——定时器
function asyncFn(fn){
setTimeout(function(){
fn(Math.random())
},(2*Math.random()*3)*1000)
}
asyncFn(function(xxx){
console.log('xxx is ')
console.log(xxx)
})
等了将近5秒钟,才打出下面的代码
xxx is
0.5311492544878877
我们可以看出,setTimeout
造成了异步,
等5秒后,作为函数asyncFn
参数的函数才调用
回调示例——请求
案例我放在github上
回调函数实例源码
案例里的原理是,
在javascript里声明一个函数callbakFn
,
然后发出一个请求,将函数名作为参数传进去xxx.js?callback=callback
,
同时请求xxx.js文件,
在服务器里文件server.js将函数名callbakFn
替换xxx.js文件里字符串{{callback}}
,
xxx.js文件,就变成
callbackFn('回调成功')
然后浏览器下载xxx.js文件,解析指向上面的代码,
就会调用callbackFn
函数,
这就是请求里的回调。
网友评论