DAY 5
# 对async/await的理解^1
async 和await是ES6的语法糖
async必须等函数内所有的await都执行完后才会发生状态的改变。
async标记的函数返回的结果是promise
await必须在async定义的函数内才可以使用
async定义的函数遇到错误或者reject等会直接发生状态改变reject出来。而后不再运行后面的代码。
async function start() { console.log('START') const time1 = 500 const time2 = await first(time1) const time3 = await Promise.reject(time2) const res = await third(err) console.log('最后一次延迟'+ res) console.log('END') // try { // const time3 = await Promise.reject(time2) // }catch(err){ // const res = await third(err) // console.log('最后一次延迟'+ res) // console.log('END') // } } start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17解决此类错误后就停止进程的办法:可以使用try/catch方法捕获异常,之后再往下继续执行代码。
async function start() { // console.log('START') // const time1 = 500 // const time2 = await first(time1) // const time3 = await Promise.reject(time2) // const res = await third(err) // console.log('最后一次延迟'+ res) // console.log('END') try { const time3 = await Promise.reject(time2) }catch(err){ const res = await third(err) console.log('最后一次延迟'+ res) console.log('END') } } start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 作用域和作用域链的理解^2
# 📖概念
作用域是运行时代码中某些特定部分中变量,函数和对象的可访问性。它是一个独立的地盘,使得变量不会外泄、暴露出去。
# 作用
隔离变量,不同作用域下同名变量不会有冲突
# 分类
# 全局作用域
在代码中任何地方都可以访问到
- 最外层函数和最外层函数外面定义的变量
- 未定义直接赋值的变量
- 所有的window对象属性拥有全局作用域
# 函数作用域
声明在函数内部的变量。只有在固定的代码片段内可以访问到。
- 内层作用域变量可以访问外层作用域变量,反之则不行
- 块语句如if和switch条件语句和while循环语句的
{}
内不会创建新的作用域
# 块级作用域
通过let和const声明,所声明的变量在指定块的作用域外无法被访问
# 创建条件
- 在一个函数内部
- 在一个代码块
{}
内部
# 概述
- let和var的语法一样,但是声明的变量不会被提升。
- 在一个块内let和const声明的变量不能重复声明
# 🐛 执行上下文与作用域的区别
作用域和执行上下文之间最大的区别是: 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。
# 对于执行上下文的理解[^3]
执行上下文是在js执行阶段中产生的
- 全局上下文:全局执行上下文只会调用一次;流程为:创建全局对象(window/global)==> 设置this的值等于全局对象
- 函数执行上下文:当函数被调用时,就会为该函数创建一个新的上下文
# 创建执行上下文
- 创建阶段:
- this值得决定,即this的绑定
- 全局执行上下文:this指向全局对象
- 函数执行上下文:this指向取决于函数被调用的地方
- 被引用对象作为方法调用:this指向该引用对象
- 否则被设为全局对象(在严格模式中被设为undefined)
- this值得决定,即this的绑定
- 执行阶段:在执行阶段如果JavaScript引擎不能在源码中声明的实际位置找到let变量的值,则会被赋值为undefined。
# 对this对象的理解[^4]
函数调用模式:如果this所处函数在全局被调用,则this指向window全局
作为引用对象的方法调用模式:this所在的函数作为引用对象的方法被调用时this指向该引用对象
构造器调用模式:
如果函数调用前使用了 new 关键字, 则是调用了构造函数。 这看起来就像创建了新的函数,但实际上 JavaScript 函数是重新创建的对象:
涉及new知识
apply、call和bind调用模式,this指向其第一个传入的参数。
- apply和call的区别:两者的第一个参数都是this的值,作用都是可以改变this的指向
- apply传入的第二个参数以数组的形式传入,或者是arguments如:
Object.apply(this,[num1,num2])
或者Object.apply(this,arguments)
- call后面的参数是逐个传递的如
Object.call(null, num1, num2)
- apply传入的第二个参数以数组的形式传入,或者是arguments如:
- apply和call的区别:两者的第一个参数都是this的值,作用都是可以改变this的指向
箭头函数:箭头函数的 this 始终指向函数定义时的 this,而非执行时。,箭头函数需要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。
# 浏览器的垃圾回收机制[^5]
# 什么是垃圾回收
垃圾回收:GC
即Garbage Collection
。“垃圾”值得是程序不是用的内存或者之前用过后续不再用的内存空间。而JS拥有自动的垃圾回收机制。即其会找出不需要的内存(变量)然后释放内存。
# 垃圾是如何产生的
如下图:js中的引用数据是保存在堆中的,然后会在栈中保存该实际对象的引用地址。所有js中操作的引用数据都是操作其对象,而不是它本身。
比如下面的代码:
var test = { name: 'test'}
代码中命名了一个变量test
,它保存了{name:'test'}
的引用地址。如果将test
重新赋值为test = [1,2,3]
则test
的引用就发生了改变,变成了后面的数组。前面的对象引用关系就没有了
则{name:'test'}
变成了无用的内存占用。如果不进行回收会一直占用内存空间。
# 垃圾是如何回收的
垃圾回收是定期的发现不再使用的内存,然后释放它。不进行实时释放是因为开销太大了
# 标记清除算法
- 标记阶段:先为所有的活动对象打上标记;
- 清除阶段:清除没有标记的(非活动对象)内存;
# 引用计数法
将对象是否不在需要简化为对象没有被其他的对象引用,也就是零引用。
他的策略就是跟踪记录每个变量值被使用的次数
# V8对于CG的优化
# 分代式垃圾回收
V8将堆内存分为新生代和老生代两个区域,然后采用不同的垃圾回收机制进行垃圾回收管理。
V8的整个堆内存大小等于新生代加上老生代的内存
- 新生代:存活时间短的对象,即新产生的对象。一般支持
1-8M
容量- 新生代垃圾回收:(
Scavenge
算法、Cheney
算法) - 简单说就是将堆内存分为两部分:
- 使用区:新加入的对象被加入使用区,当使用区快被写满时就进行一次垃圾清理操作
- 空闲区:垃圾回收时对使用区的活动对象进行标记,然后将其复制到空闲区并排序。之后将使用区清空,进入垃圾清理阶段。最后进行角色互换。将使用区变成空闲区,空闲区则成为了使用区。
- 进行了多次复制还存活下来的对象会被认为是老生代。或者空闲区占用超过25%则直接晋升老生代。
- 新生代垃圾回收:(
- 老生代:存活时间长或者常驻内存的对象,即经历过了新生代的垃圾回收还存活下来的对象。
- 采用标记清除算法,定时对其进行垃圾清除。V8使用了标记整理算法解决了标记清除算法导致产生大量不连续内存碎片的问题。
# 闭包
我将永远记住闭包的方法是通过背包的类比。当一个函数被创建并传递或从另一个函数返回时,它会携带一个背包。背包中是函数声明时作用域内的所有变量。
# 参考文章
[^3]:[译] 理解 JavaScript 中的执行上下文和执行栈 (opens new window) [^4]:this、apply、call、bind (opens new window) [^5]:「硬核JS」你真的了解垃圾回收机制吗 (opens new window)