JS总结笔记(@神三元灵魂之问下)

1,707 阅读7分钟

数据如何储存

  1. 栈:
  • Number;
  • Boolean;
  • String;
  • null;
  • undefined;
  • bigint;
  • symbol
  1. 堆:
  • 所有的对象类型数据
  1. 为什么不都用栈呢(开销巨大)
  • 对于系统栈而言,除了保存变量;
  • 还有创建并切换函数上下文的功能;

V8引擎如何进行垃圾内存的回收

  1. 对于栈:
  • 当上下文切换后,栈顶空间会被自动回收;
  1. 对于堆:
  • 当我们构造一个对象进行赋值操作时,其实相应的内存已经被分配到堆上了;我们可以不断的构造对象,直到堆的内存上限
  • 为何要有堆的内存上限:JS的单线程和JS的垃圾回收机制

  • 新生代内存:

    • 由于在堆中内存是连续分配的,这样做是为了防止内存碎片--- Scavenge算法
  • 老生代内存:

    • 如何晋升为老生代:
    1. 已经经历过一次Scavenge回收;
    2. 新生代的闲置内存占用超过25%;
    • 老生代垃圾回收:
    1. 进行标记-清除:标记阶段--遍历堆内存中的所有对象并进行标记,然后将使用的变量强引用的变量取消标记;清除阶段--将剩下标记的变量进行删除,释放内存。(依旧会产生内存碎片)
    2. 整理内存碎片。 清除阶段后,把存活的对象全部往一端靠拢。(非常耗时)
  • 增量标记:由于老生代内存垃圾处理耗时很长,为防止阻塞。V8将标记任务分成很多小部分处理。

V8执行一段代码的过程

  1. 通过语法分析和词法分析生成AST(抽象语法树)
  2. 将AST转化为字节码(直接解析成机器码消耗内存);
  3. 解释器逐行执行字节码,遇到热点代码(多次出现)由编译器生成相应的机器码,以优化执行效率。

EventLoop -- 宏任务和微任务

1.在JS中,大部分的任务都是在主线程上执行的,常见的有:

  1. 渲染事件;
  2. 用户交互事件;
  3. js脚本执行;
  4. 网络请求、文件读写完成事件等;

V8用队列去存储这些任务:宏任务(普通任务和延迟任务--setTimeout/setInterval)和微任务(Promise\Promise.then)

  1. 解决异步回调:
  • 将异步回调进行宏任务队列的入队操作;(易造成应用卡顿)
  • 将异步回调放到当前宏任务的末尾;
  1. 补充web worker(解决单线程导致主线程任务量过大,而渲染缓慢的问题): www.ruanyifeng.com/blog/2018/0…

2.EventLoop -- node.js

  1. timer阶段(执行到时间的定时器)
  2. I/O异常回调阶段(未立即等待到异步事件的响应)
  3. 空闲预备阶段(等到第二阶段结束,poll阶段未开始)
  4. poll(轮询)
  5. check阶段(执行setImmiate的回调--立即结束长时间任务)
  6. 关闭事件的回调阶段

3.node.js 与 浏览器关于eventloop的区别

  • 浏览器的微任务是在每一次宏任务结束后执行的;
  • node.js的微任务是在不同阶段之间执行的;

4.关于process.nextTick

  • 独立于eventloop的任务队列;
  • 在每一个eventloop阶段完成后会去检查这个队列,若有则优于微任务队列执行;

node.js的异步、非阻塞I/O是如何实现的

  1. 浏览器中只有一种I/O,即使用Ajax发送网络请求。 ---网络I/O
  2. node.js中:
  • 文件I/O;
  • 网络I/O

1. 非阻塞I/O和阻塞I/O

  • 非阻塞I/O:调用后立即返回;
  • 阻塞I/O:等到操作系统的操作全部完成后,再返回;(消耗性能)

2. 非阻塞I/O:如何通知node.js,操作系统已经完成操作了呢?

轮询:重复判断操作系统是否已经完成操作;

  • 一直轮询查看I/O的状态;
  • 遍历文件描述符(文件I/O时操作系统与node.js之间的文件凭证);
  • epoll模式,即进入轮询时,若I/O未完成,CPU就休眠;完成后唤醒;
以上方法都非常消耗CPU。

3. 异步I/O本质(线性池)

  1. fs.readFile调用node.js的核心模块fs.js;
  2. node.js调用内建模块,创建对应文件的I/O观察者对象
  3. 根据不同平台,内建对象通过libuv中间层进行系统调用;
  4. libuv中间层进行系统调用
  • 创建请求对象
  • 推入线性池,调用返回;
  • 回调通知;I/O观察者取出请求对象的存储结果,同时取出它的oncomplete_sym属性,将前者作为函数参数传递给后者,并执行。---完成回调
线性池:一个线程进行计算,剩下的进行I/O操作。I/O完成后,通知计算的线程。

异步编程的方案

1. 回调函数(高级函数)

弊端:

  1. 回调地狱;

内部实现机理(发布-订阅模式)

  • addListener;
  • removeListener;
  • once;
  • removeAllListener;
  • emit

2. Promise(消灭了回调地狱)

亮点:

  1. 回调函数延迟绑定;(通过后面的then方法传入)
  2. 返回值穿透;(根据then中传入值创建不同类型的Promise,然后把返回的Promise穿透到外层,以供后续调用)
  3. 错误冒泡;(错误一直向后传递,被catch捕捉到)

为什么引入微任务:

  1. Promise中执行函数是同步的,但里面存在异步操作。
  2. 在异步操作结束后,会调用resolve方法;或执行中途出错,则调用reject方法。这两者都是作为微任务进入到EventLoop中。

如何实现链式调用:

Promise本质:有限状态机

错误捕捉及冒泡机制:

  1. catch方法捕捉错误
  2. 冒泡机制
    onRejected = typeof onRejected === 'function' ? onRejected : error => {throw error} 一旦其中一个PENDING状态的Promise出现错误后状态必然变为失败,然后执行onRejected函数,而这个onRejected执行又会抛错;新的Promise的状态变为失败;依次执行下去,直到catch捕捉到这个错误,才停止往下抛。

实现Promise的resolve、reject和finally

  1. Promise.resolve
  • 传参为一个Promise,则直接返回它;
  • 传参为一个thenable对象,返回的Promise会跟随这个对象,将他的最终状态作为自己的对象;
  • 传参为其他对象,返回成功状态的Promise对象
  1. Promise.reject
  • 传入的参数作为一个reason原封不动的往下传
  1. Promise.finally
  • 无论成功还是失败,都会调用finally中的函数,并将值原封不动的往下传

实现Promise的all和race

  1. Promise.all
  • 传入的参数为一个空的可迭代对象,则直接进行resolve;
  • 如果传参中有一个promise失败,那么Promise.all返回的promise对象失败;
  • 在任何情况下,Promise.all返回的promise的完成状态的结果都是一个数组;
  1. Promise.race
  • 只要有一个promise执行完,直接resolve并停止执行;

3. co + Generator方式

特点:

  • 用co库让代码一次完成,同时以同步的方法执行;

async + awaite

特点:

  • 每一个async函数都返回一个Promise对象;

生成器以及协成的理解

1. 生成器执行流程

2. yield*

3. 生成器实现机制---协程

  1. 线程和进程
  • 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
  1. 协程
  • 协程处于线程的环境中,一个线程可以存储多个协程--> 可以将协程理解为线程的一个个任务
  • 协程不受操作系统的管理
  1. 协程的运作过程(单个执行)

  • A将执行权交给B(A启动了B),A为B的父协程;
  • B最后return 100,其实是将100传给了A
  • 对于协程,它不受操作系统的控制,完全有用户自定义切换。因此并没有线程和进程的上下文开销,因而高性能

4. 如何让Generator的异步代码顺序执行

1. Generator如何与异步产生关系;

2. 怎么把Generator顺序执行完毕;

  1. thunk函数(偏函数):
  • 核心逻辑:接受一定的参数,产生出定制化的函数,然后使用定制化的函数去完成功能;
  1. Generator和异步(核心:绑定回调函数)
  • thunk版本
  • Promise版本
  • 采用co库

async和await的运行机制

1. async

  1. 概念: 一个通过异步执行并隐式返回Promise作为结果的函数;

2. await

forEach中用await会产生什么问题,怎么解决

无法保证异步代码,顺序执行

  • 由于forEach的底层原理:直接执行,因此如果后面的任务用时短,就会提前执行。

for...of解决 --- 迭代器

  • 解决原理:Iterator--迭代器