闭包
(面试时候一般扯到垃圾回收机制和作用域链)
闭包看了阮一峰老师的,简单易懂,但是要我解释闭包究竟是什么,我也不知该怎么解释,看了红皮书的《JavaScript 高级编程设计》,这里记一下如果哪里有理解错误的,请指出 ~
概念 : 有权访问另外一个函数作用域中变量的函数
特性 : 函数内嵌套函数,内部函数可引用外层参数和变量,参数和变量不会被垃圾回收机制回收
作用链 : 就是变量和函数可访问范围,变量只能向上访问,访问到 window 对象则被终止
作用链:就是变量和函数可以访问的范围,遍历只能向上访问,访问到window对象被仅仅只
原型链 : 每个对象都会有一个原型proto ,只有函数对象才会有 prototype, 当我们访问一个对象的属性时,如果这个对象的内部没有这个属性时,就会去proto 中查找这个属性,这个proto 又有自己的proto,于是一直查找下去,这就是原型链
简单理解 : 函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 的变量,函数 B 就被称为闭包。 闭包: 有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式,就是在一个函数内部创建另一个函数; 当某个函数被调用时,会创建一个执行环境以及相应的作用域链,然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象,但在作用域链中,外部函数的活动对象是种处于第二位,外部函数的外部函数的活动对象处于第三位...一直到作为作用域链终点的全局执行环境。
来看个例子:
kotlin
function compare(value1, value2) {
if (value1 < value2) {
return -1
} else if (value1 > value2) {
return 1
} else {
return 0
}
}
var result = compare(5, 10)
下面的图,表示了 compare() 函数执行时的作用域链。首先定义了 compare()函数,然后在全局作用域中调用了它。调用 compare() 函数的时候,会创建一个包含 argumetns
、value1
、value2
的活动对象。全局执行环境的变量对象(包含 result 和 compare)在 compare()执行环境的作用域链中则处于第二位
闭包
(面试时候一般扯到垃圾回收机制和作用域链)
闭包看了阮一峰老师的,简单易懂,但是要我解释闭包究竟是什么,我也不知该怎么解释,看了红皮书的《JavaScript 高级编程设计》,这里记一下如果哪里有理解错误的,请指出 ~
概念 : 有权访问另外一个函数作用域中变量的函数
特性 : 函数内嵌套函数,内部函数可引用外层参数和变量,参数和变量不会被垃圾回收机制回收
作用链 : 就是变量和函数可访问范围,变量只能向上访问,访问到 window 对象则被终止
作用链:就是变量和函数可以访问的范围,遍历只能向上访问,访问到window对象被仅仅只
原型链 : 每个对象都会有一个原型proto ,只有函数对象才会有 prototype, 当我们访问一个对象的属性时,如果这个对象的内部没有这个属性时,就会去proto 中查找这个属性,这个proto 又有自己的proto,于是一直查找下去,这就是原型链
简单理解 : 函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 的变量,函数 B 就被称为闭包。 闭包: 有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式,就是在一个函数内部创建另一个函数; 当某个函数被调用时,会创建一个执行环境以及相应的作用域链,然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象,但在作用域链中,外部函数的活动对象是种处于第二位,外部函数的外部函数的活动对象处于第三位...一直到作为作用域链终点的全局执行环境。
来看个例子:
kotlin
function compare(value1, value2) {
if (value1 < value2) {
return -1
} else if (value1 > value2) {
return 1
} else {
return 0
}
}
var result = compare(5, 10)
下面的图,表示了 compare() 函数执行时的作用域链。首先定义了 compare()函数,然后在全局作用域中调用了它。调用 compare() 函数的时候,会创建一个包含 argumetns
、value1
、value2
的活动对象。全局执行环境的变量对象(包含 result 和 compare)在 compare()执行环境的作用域链中则处于第二位
作用域链的本质就是一个指向变量对象的指针列表
函数内部定义的函数会将傲寒函数的活动对象添加到他的作用域链当中,
在一个函数内部定义的函数会将包含函数的活动对象添加到他的作用域链当中
全局环境得变量对象始终存在,而像 compare() 函数这样的局部环境的变量对象,则只在函数执行的过程中存在。在创建 compare() 函数时,会先创建一个预先包括全局变量对象的作用域链,这个作用域链被保存在内部的 [[ Scope ]] 属性中。
当调用 compare() 函数的时候,会为函数创建一个执行环境,然后通过复制函数中的 [[ Scope ]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并推入执行环境作用域链的前端。 (也就是作用域链的前端是 compare 的活动对象)
对于例子中的 compare()函数的执行环境来说,其作用域链中包含两个变量对象: 本地活动对象和全局变量对象。显然,作用域链的本质是一个指向变量对象的指针列表
一般来讲,当函数执行完毕之后,局部活动对象就会被销毁,内存中仅保存着全局作用域,但是闭包不同,它会将活动对象添加到作用域链的前端,也就是说,局部活动对象被销毁,但是它的活动对象仍然留在内存中,这也就是为什么使用闭包可能会导致内存问题。因为闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存
在一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中,例如下边代码
php
function createComparosonFunction(propertyName) {
return function(object1, object2) {
var value1 = object1[propertyName]
var value2 = object2[propertyName]
if (value1 < value2) {
return -1
} else if (value1 > value2) {
return 1
} else {
return 0
}
}
}
var compare = createComparosonFunction('name')
var result = compare({ name: 'PDK' }, { name: '彭道宽' })
在匿名函数从 createComparosonFunction() 被返回时,它的作用域被初始化为包含 createComparosonFunction() 函数的活动对象和全局变量对象,这样,匿名函数就可以访问在 createComparosonFunction() 中定义的所有变量。
最重要的是,createComparosonFunction() 执行完之后,它的活动对象不会被销毁,为什么呢?因为匿名函数的作用域链仍然在引用它的活动对象。换句换说,当 createComparosonFunction()函数执行完毕之后,局部活动对象就会被销毁,但是因为闭包的原因,它的作用域链被添加到了作用域链的前端,导致 createComparosonFunction()的活动对象会留在内存中,知道匿名函数被释放,createComparosonFunction()的活动对象才会被销毁。比如:
php
// 创建函数
var compareName = createComparosonFunction('name')
//调用函数
var result = compareName({ name: 'PDK' }, { name: '彭道宽' })
// 解除对匿名函数的引用 (以便释放内存)
compareName = null
设置 compareName 为 null,是为了解除对函数的引用,等于通知垃圾回收机制将其回收,随着匿名函数的作用域链被销毁,其他作用域 (除了全局作用域)也都可以安全地销毁了
在一个函数内部定义的函数会将包含函数的活动对象添加到他的作用域链当中
注意: 作用域链的这种配置机制,引出了一个副作用,即闭包只能取得包含函数中任何变量的最后一个值
强调: 任何变量的最后一个值
javascript
function createFunctions() {
var result = new Array()
for (var i = 0; i < 10; i++) {
result[i] = function() {
return i
}
}
return result
}
从表面上看,似乎每个函数都应该有自己的索引值, 即位置 0 的函数返回 0,1 的函数返回 1, 但实际上,每个函数都返回 10,因为每个函数的作用域链中都保存着 createFunctions() 函数的活动对象,所以它们引用的都是同一个变量 i,当 createFunctions()函数被返回,变量 i 的值是 10,由于作用域链的副作用,每个函数都引用着保存变量i的同一个对象
。
javascript
解决方式,创建另一个匿名函数
function createFunctions() {
var result = new Array()
for (var i = 0; i < 10; i++) {
result[i] = function (num) {
return function () {
return num
}
}(i)
}
return result
}
在上述代码中,没有立即将闭包赋给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数 num,也就是最终的函数要返回的值。在调用每个匿名函数时,我 们传入了变量 i。由于函数参数是按值传递的,所以就会将变量 i 的当前值复制
给参数 num。而在这个 匿名函数内部,又创建并返回了一个访问 num 的闭包。这样一来,result 数组中的每个函数都有自己 num 变量的一个副本,因此就可以返回各自不同的数值了
闭包与 this 对象
this 对象是在运行时基于函数的执 行环境绑定的: 在全局函数中,this 等于 window,而当函数被作为某个对象的方法调用时,this 等于那个对象
。不过,匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window,(在使用 call 和 apply 改变函数执行环境下,this 会指向其他对象)。但有时候,由于编写闭包的方式不同,这一点可能不会那么明显
javascript
var name = "The Window"
var object = {
name : "My Object",
getNameFunc : function () {
console.log('@@@@', this) // 执行 object
return function () {
console.log(this) // 指向 window
return this.name
}
}
}
console.log(object.getNameFunc()()) "The Window"(在非严格模式下)
// 把外部作用域中的this对象保存在一个闭包能访问得到的变量里,这样就能让闭包访问该对象了
var name = "The Window"
var object = {
name : "My Object",
getNameFunc : function () {
console.log('@@@@', this) // 执行 object
let _this = this
return function () {
console.log(this) // 指向 window
console.log(_this) // 指向 object
return _this.name
}
}
}
console.log(object.getNameFunc()()) "My Object"
为什么匿名函数没 有取得其包含作用域(或外部作用域)的 this 对象呢 ?
每个函数在被调用时都会自动取得两个特殊变量:
this
和arguments
。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。
(怎么理解这句话?),个人的理解: 在执行过程中,每个函数都会有一个执行环境,在 getNameFunc()函数里的执行环境 this 指向的是 object,而在闭包中,闭包又有自己的执行环境,而这里的this与它外部函数getNameFunc()的this是不相等的
,可能在某种情况下,它们都指向 window,但是并不能说它们相等,而上述代码里,在定义匿名函数前,把 this 对象赋值给了 _this 变量,而在定义了闭包之后,闭包可以访问到外部函数的变量,即使在函数返回之后,闭包将活动对象添加到作用域链的前端,_this 仍然引用着 object,所以会打印出 "My Object"
为什么闭包中的 this=window,因为通过闭包可以访问外部函数作用域中的变量。但每个函数在被调用时都会自动取得两个特殊变量: this 和 arguments。内部函数在搜索这两个变量时,只会搜索到其自己活动对象为止(找到了就不用沿着作用域链继续找了)因此永远不可能直接访问外部函数中的这两个变量。匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。
匿名函数与闭包
什么是匿名函数 ?一般用到匿名函数都是立即执行的,通常叫做自执行匿名函数或者自调用匿名函数。常用来构建沙箱模式,作用是: 开辟封闭的变量作用域环境
。我们来看几个例子
javascript
;(function() {
console.log('我是匿名方式1')
})()(
//我是匿名方式1
(function() {
console.log('我是匿名方式2')
})()
)(
//我是匿名方式2
function(i, j, k) {
console.log(i + j + k)
}
)(1, 3, 5) // 9
实际上,立即执行的匿名函数并不是函数 ,因为已经执行过了,所以它是一个结果,这个结果是对当前这个匿名函数执行结果的一个引用(函数执行默认return undefined
)。这个结果可以是一个字符串、数字或者 null/false/true,也可以是对象、数组或者一个函数(对象和数组都可以包含函数),当返回的结果包含函数时,这个立即执行的匿名函数所返回的结果就是典型的闭包了。
用匿名函数实现闭包
javascript
var func = (function() {
var a = 10
return function() {
console.log(a)
}
})()
func() // 10
// func 作为立即执行匿名函数执行结果的一个接收,这个执行结果是闭包,func等于这个闭包。
// 执行func就相当于执行了匿名函数内部return的闭包函数
// 这个闭包函数可以访问到匿名函数内部的私有变量a,所以打印出10
所以,我们可以说: 闭包跟函数是否匿名没有直接关系,匿名函数和具名函数都可以创建闭包 !!!
javascript
// 经典面试题,循环中使用闭包解决 var 定义函数的问题
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
// 首先因为 setTimeout 是个异步函数,所有会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。
// 解决方式一: 闭包
for (var i = 1; i <= 5; i++) {
;(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
// 方式二: 使用 setTimeout 的第三个参数, 第三个参将作为第一个参数函数func的参数传进去。
for (var i = 1; i <= 5; i++) {
setTimeout(
function timer() {
console.log(i)
},
i * 1000,
i
)
}
// 方式三:利用let, let他会创建一个块级作用域
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
javascript
function fetch(a) {
return function test1() {
return function test2() {
var a = 5
return a
function a() {}
}
}
}
let res = fetch(55)(2)(3)
console.log(res) // 5, 如果把var a = 5 去掉,那么return 的是 function a
function fetch1(a) {
return function() {
return a
}
}
console.log(fetch1(100)(2)) // 100
function fetch2(a) {
return function(a) {
return a
}
}
console.log(fetch2(100)(2)) // 2
把外部作用域当中的this对象保存在一个闭包能够访问得到的变量里面
func作为立即执行匿名函数执行结果的一个接受,这个执行结果是闭包,执行了匿名函数内部的reteurn的闭包函数
前端监控及前端埋点
xml
<script>
(function (win, doc) {
function setFontSize () {
var winWidth = window.innerWidth;
doc.documentElement.style.fontSize = (winWidth / 750) * 100 + 'px'
}
var event = 'onorientationchange' in win ? 'orientationchange' : 'resize'
var timer = null
win.addEventListener(event, function (){
clearTimeout(timer)
timer = setTimeout(setFontSize, 300)
}, false)
win.addEventListener('pageshow', function(e) {
if (e.persisted) {
clearTimeout(timer)
timer = setTimeout(setFontSize, 300)
}
})
setFontSize()
})(window, document)
</script>
```
linux服务器报No space left on device错误的解决过程
起因
今天本来高高兴兴的想把 npm run build
后的代码 push 到服务器上,然后它居然报错了!!!
cp: /var/xxx/xxx/js/chunk-vendors.f1b81010.js.map: No space left on device
解决过程
作为一个前端工程师,对于运维这玩意,嗯,只能找百老师了,于是去百度查了一下,之后看了很多文章,终于解决
- df -h // 查看服务器磁盘使用空间
不出啥意外的话,你会看到这种玩意
Filesystem | Size | Used | Availd | Use% | Mounted on |
---|---|---|---|---|---|
udev | 414M | 0 | 414M | 0% | /dev |
tmpfs | 87M | 9.4M | 78M | 11% | /run |
... | ... | ... | ... | ... | ... |
/dev/vdal | 50G | 47G | 0 | 100% | / |
... | ... | ... | ... | ... | ... |
卧槽,什么鬼,这个 /dev/vdal
居然用了 47G !!!这是个啥?于是我去看了下哪个目录占用空间大(不出意外就是这个 /dev/vdal
)
- sudo du -sh * // 查看文件夹占用内存
然后从中我发现了这个玩意
16M | bin |
... | ... |
44G | home |
... | ... |
果然,在 home 下边占用了 44G 的磁盘内存,但是知道了哪个文件夹占用那么大的内存有什么用?我不会解决啊!!!还是先查一下,超过 100M 大小的大文件,看看有没有什么收获
- sudo find / -size +100M -exec ls -lh {} ; // 查找超过100M的文件
然后结果如下
-rw-rw-r-- 1 ubuntu ubuntu 32G Nov 27 06:16 /home/ubuntu/.pm2/logs/app-error.log -rw-rw-r-- 1 ubuntu ubuntu 12G Nov 27 06:03 /home/ubuntu/.pm2/pm2.log -r-------- 1 root root 128T Nov 27 16:13 /proc/kcore
卧槽,pm2,好熟悉,记起来了,之前通过 pm2 去守护进程,但是好像,没能关闭!!!于是去查看了一下这个 .log
文件
- cd /home/ubuntu/.pm2
- sudo lsof | grep 'pm2.log'
... | ... | ... | ... | ... | ... | ... | ... | ... |
PM2\x20v3 | 19973 | ubuntu | 1w | REG | 253,1 12429815808 | 452913 | /home/ubuntu/.pm2/pm2.log | |
... | ... | ... | ... | ... | ... | ... | ... | ... |
node | 19973 | 19975 | ubuntu | 25w | REG | 253,1 0 | 452922 | /home/ubuntu/.pm2/logs/sever-error.log |
... | ... | ... | ... | ... | ... | ... | ... | ... |
列表中有很多进程都在打开该文件,虽然文件删除了,但是打开该文件的进程没有关闭,也就是说文件实际上还是存在,rm 仅仅是删除了该文件的标记。也就是说,就是有些文件删除时还被其它进程占用,此时文件并未真正删除,只是标记为 deleted,只有进程结束后才会将文件真正从磁盘中清除。
然后,我就将这两个 .log
文件删除了。。。通常的话应该是 把占用文件的相关进程关闭
或者 以清空的方式替代删除
- rm pm2.log logs/app-error.log
相关链接
df 与 du 不一致情况分析 : blog.csdn.net/carolzhang8...
centos 如何清除 /dev/vdal 系统盘 : www.cnblogs.com/xjxz/p/6085...
No space left on device : blog.csdn.net/WuZuoDingFe...
RSA 加密
一、什么是 RSA 加密
二、RSA 加密流程
三、相关库
DNS 了解
介绍
(其实不想介绍,因为这个也是我百度百科查的...)
DNS 全称 :Domain Name System,域名服务系统,它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。
既然说是域名服务系统,我们得先知道,什么是域名? 比如下边这些,都可以说是域名 👇
www.pengdaokuan.cn www.pdk.com www.seewo.com www.class.seewo.com www.cvte.com
那么,我们从一个简单的问题入手,域名是如何注册的?
域名注册流程
- 域名注册人向转销商进行购买域名服务
- 转销商代表注册人进行注册,但没有与 ICANN 签订合同的关系
- 经过 ICANN 认证的组织(经销商)来处理域名注册,将需要请求的域名及域名的联系方式和技术信息发送给注册管理机构
- 注册管理机构会在注册表上记录 Whois 的联系方式,还会将此域名文件添加到主服务器。
- 更新所有信息,此域名即被视为已注册且可以使用。
👉 你可以通过这里了解: WHOIS
DNS 为什么要通过 IP 确认通信对象
ok,按照上边的流程,我们注册了一个域名 www.pengdaokuan.cn (没钱维护,已经die了)
,然后呢,我们知道,在浏览器中,输入 IP
或者 域名
,都可以访问我们的目的网站,既然如此,为什么不通过域名去确认通信对象,而是通过 IP 呢?
不要跟我说,因为 TCP/IP 协议规定要知道目的 IP 地址,所以就选择 IP 确认通信 ~ 当然,这应该也是一方面的原因,但是具体的原因如下 👇
📢 从运行效率上来讲,IP 地址是 32 比特,也就是 4 字节,而域名,最短也要十几个字节,最长的时候,可能达到 255 字节,换句话讲,使用 IP 地址只需要处理 4 字节的数字,现在用域名,就要处理十几甚至 255 个字节的字符,这严重影响效率。同时添加了路由器的负担,传送数据也会花费更长的时间。
比如我发送一个 2KB 的字符串,中间有可能通过光纤传递传递,也有可能其他的方式,如早期的铜缆。不同的连接材料带宽不同。有可能一个包的 maxSize 只能 1KB。应用层的数据在通讯过程中会根据通讯设备的带宽分成很多个包。一个 http 请求并不是只发了一个包,可能一次请求被拆成了很多个包。所以如果用域名,网络请求的数据量会增加很多。
这时,有键盘侠
出来发言了 : "那使用高性能路由器不就能解决这个问题了吗"
ok,但是要知道,路由器的速度是有极限的,互联网内部流动的数据量已经让路由器疲于应付了。虽然随着技术发展,路由器性能会不断提升,但是与此同时,数据量也以更快的速度在增长。所以说,用域名确认通信对象不是一个明智的选择 ~
DNS 可否使用 TCP
我们常说,DNS 使用 UDP 进行传输,那么 DNS 可不可以使用 TCP 呢?
通过百度百科,我们知道,DNS 使用的是 53 端口,是可以通过 TCP 和 UDP 进行传输,只是说,大部分时候都用的 UDP 协议,但在以下两种情况,会以 TCP 协议进行传输 :
- 返回的响应超过 512 字节(不知道为啥超过 512 就要用 TCP 的,拖出去砍了)
- 区域传送
区域传送
可能此时此刻,你在想,我特么知道区域传送是啥有什么用,我一个前端仔,只要知道 DNS 解析 url 的流程就完事了嘛,ok,如果是这样想的,可以直接滚动到下边啦 ~
DNS 区域传送
(DNS zone transfer)指的是一台备用服务器使用来自主服务器的数据刷新自己的域(zone)数据库,目的是为了做冗余备份,防止主服务器出现故障时 dns 解析不可用。
📌 区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据,辅域名服务器会定时向主域名服务器进行查询,以便了解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。
既然说到了传送变动的那部分数据,那么不得不提区域文件了...科普一下概念
DNS区域(ZONE):DNS域名空间中连续的树,将域名空间按照需要划分为若干较小的管理单位。
DNS服务器中,必须先建立区域,在区域中建立子域,在区域或者子域中添加主机记录。
存储区域数据的文件,称为区域文件。一台DNS服务器上可以存放多个区域文件,同一个区域文件也可以存放在多台DNS服务器上。
区域文件包含主机名和对应IP地址、刷新间隔和过期时间等信息。
区域文件通常只配置一个域名,区域名字即为域名。它可以包含多个记录。它的表达形式为 :一条记录就是一条从资源到名字的单一映射。
Event Loop
众所周知 JS 是门非阻塞单线程
语言,因为在最初 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,我们在多个线程中处理 DOM 就可能会发生问题(一个线程中新加节点,另一个线程中删除节点),当然可以引入读写锁解决这个问题
JS 在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。如果遇到异步的代码,会被挂起并加入到 Task(有多种 task) 队列中。一旦执行栈
为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所以本质上来说 JS 中的异步还是同步行为
javascript
console.log('start');
setTimeout(function () {
console.log('I am setTimeout');
}, 0);
console.log('end');
// start
// end
// I am setTimeout
以上代码虽然 setTimeout 延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,不足会自动增加。所以 setTimeout 还是会在 script end 之后打印
不同的任务源会被分配到不同的 Task 队列中,任务源可以分为 微任务
(microtask)和 宏任务
(macrotask)。在 ES6 规范中,microtask 称为 jobs,macrotask 称为 task, 比如 ES6 中的 Promise 异步属于微任务
javascript
console.log('start');
setTimeout(function () {
console.log('I am setTimeout');
}, 0);
new Promise((resovle, reject) => {
console.log('Promise');
resolve();
})
.then((res) => {
console.log('promise 1');
})
.then((res) => {
console.log('promise 2');
});
console.log('end');
// start
// Promise
// end
// promise 1
// promise 2
// I am setTimeout
上述代码首先执行同步代码的 start,之后遇到 setTimeout,由于 setTimeout 是宏任务,也就是放到 Task 队列中,接着执行同步代码 new Promise,打印 ' Promise ' ,Promise 属于微任务,同样被放在 Task 队列中,接着执行同步代码 end,之后执行栈为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,这里有个误区,认为微任务快于宏任务,其实是错误的。因为宏任务中包括了 script ,浏览器会先执行一个宏任务,接下来有异步代码的话就先执行微任务
所以正确的一次 Event loop 顺序是这样的
- 执行同步代码,这属于宏任务
- 执行栈为空,查询是否有微任务需要执行
- 执行所有微任务(微任务是追加在本轮循环中的)
- 必要的话渲染 UI
- 然后开始下一轮 Event loop,执行宏任务中的异步代码
通过上述的 Event loop 顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作 DOM 的话,为了更快的 界面响应,我们可以把操作 DOM 放入微任务中。