Better

Ethan的博客,欢迎访问交流

来自 MDN 的 JavaScript 笔记

最近碰到奇怪的问题,翻阅 MDN 文档,发现其可真是个宝藏哈。内容都是些基础的东西,包括事件循环、类型数组、内存管理和浏览器进程

事件循环

JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。

运行时概念

  • 栈:函数调用形成了一个由若干帧组成的栈
  • 堆:对象被分配在堆中,堆是一个用来表示一大块(通常是非结构化的)内存区域的计算机术语
  • 队列:一个 JavaScript 运行时包含了一个待处理消息的消息队列。每一个消息都关联着一个用以处理这个消息的回调函数。函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。

事件循环

  • 每一个消息完整地执行后,其它消息才会被执行。这为程序的分析提供了一些优秀的特性,包括:当一个函数执行时,它不会被抢占,只有在它运行完毕之后才会去运行任何其他的代码,才能修改这个函数操作的数据。
    • 这个模型的一个缺点在于当一个消息需要太长时间才能处理完毕时,Web 应用程序就无法处理与用户的交互,例如点击或滚动。
    • 一个良好的习惯是缩短单个消息处理时间,并在可能的情况下将一个消息裁剪成多个消息。
  • 添加消息:每当一个事件发生并且有一个事件监听器绑定在该事件上时,一个消息就会被添加进消息队列。如果没有事件监听器,这个事件将会丢失。
  • 多个运行时互相通信:一个 web worker 或者一个跨域的 iframe 都有自己的栈、堆和消息队列。两个不同的运行时只能通过 postMessage 方法进行通信。如果另一个运行时侦听 message 事件,则此方法会向该运行时添加消息。

类型化数组

JavaScript 类型化数组是一种类似数组的对象,并提供了一种用于访问原始二进制数据的机制。

不要把类型化数组与正常数组混淆,因为在类型数组上调用 Array.isArray() 会返回 false。此外,并不是所有可用于正常数组的方法都能被类型化数组所支持(如 pushpop)。

类型数组架构,为了达到最大的灵活性和效率,JavaScript 类型数组将实现拆分为缓冲和视图两部分。

  • 缓冲:一个缓冲(由 ArrayBuffer 对象实现)描述的是一个数据块。缓冲没有格式可言,并且不提供机制访问其内容。
  • 视图:为了访问在缓冲对象中包含的内存,你需要使用视图。视图提供了上下文 — 即数据类型、起始偏移量和元素数 — 将数据转换为实际有类型的数组。

ArrayBuffer

  • 一种数据类型,用来表示一个通用的、固定长度的二进制数据缓冲区
  • 不能直接操纵一个 ArrayBuffer 中的内容;你需要创建一个类型化数组的视图或一个描述缓冲数据格式的 DataView,使用它们来读写缓冲区中的内容

读写缓冲区中内容的两种方式

  • 类型数组视图:类型化数组视图具有自描述性的名字和所有常用的数值类型像 Int8,Uint32,Float64 等等。
  • 数据视图:DataView 是一种底层接口,它提供有可以操作缓冲区中任意数据的读写接口。这对操作不同类型数据的场景很有帮助

内存管理

垃圾回收的两种算法

引用计数垃圾收集:最初级的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。

存在限制:循环引用,看下面的例子

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

两个对象被创建,并互相引用,形成了一个循环。它们被调用之后会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。

标记-清除算法:这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。

假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。

从 2012 年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对 JavaScript 垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。

循环引用不再是问题了:在上面的示例中,函数调用返回之后,两个对象从全局对象出发无法获取。因此,他们将会被垃圾回收器回收。

渲染进程

渲染进程包含了哪些线程

  • GUI 渲染线程
    • 负责页面渲染,布局和绘制
    • 页面需要重绘和回流时,该线程就会执行
    • 与 JS 引擎线程互斥,防止渲染结果不可预测
  • JS 引擎线程
    • 负责处理解析和执行 JS 脚本程序
    • 只有一个 JS 引擎线程
    • 与 GUI 渲染线程互斥,防止渲染结果不可预期
  • 事件触发线程
    • 用来控制事件循环(鼠标点击、setTimeout、ajax等)
    • 当事件满足触发条件时,将事件放入到 JS 引擎所在的执行队列中
  • 定时触发器线程
    • setTimeout 和 setInterval 所在的线程
    • 定时器并不是由 JS 引擎计时的,是由定时触发线程来计时的
    • 计时完毕,通知事件触发线程
  • 异步 HTTP 请求线程
    • 浏览器有一个单独的线程用于处理 ajax 请求
    • 当请求完成时,若有回调函数,通知事件触发线程


留言