异步、单线程、非阻塞、回调

2017-08-16  本文已影响0人  林立镇

异步

先看一下下面对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支持

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函数,
这就是请求里的回调。

上一篇 下一篇

猜你喜欢

热点阅读