17K star 仓库,解决 90% 的大厂基础面试题( 六 )

  • 定时器是准时的吗?
  • 模块化当下模块化主要就是 CommonJS 和 ES6 的 ESM 了,其它什么的 AMD、UMD 了解下就行了 。
    ESM 我想应该没啥好说的了,主要我们来聊聊 CommonJS 以及 ESM 和 CommonJS 的区别 。
    CommonJSCommonJs 是 Node 独有的规范,当然 Webpack 也自己实现了这套东西,让我们能在浏览器里跑起来这个规范 。
    // a.jsmodule.exports = {a: 1}// orexports.a = 1// b.jsvar module = require('./a.js')module.a // -> log 1在上述代码中,module.exports 和 exports 很容易混淆,让我们来看看大致内部实现
    // 基本实现var module = {exports: {} // exports 就是个空对象}// 这个是为什么 exports 和 module.exports 用法相似的原因var exports = module.exportsvar load = function (module) {// 导出的东西var a = 1module.exports = areturn module.exports};根据上面的大致实现,我们也能看出为什么对 exports 直接赋值不会有任何效果 。
    对于 CommonJS 和 ESM 的两者区别是:
    • 前者支持动态导入,也就是 require(${path}/xx.js),后者使用 import()
    • 前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大 。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
    • 前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次 。但是后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
    垃圾回收本小结内容建立在 V8 引擎之上 。
    首先聊垃圾回收之前我们需要知道堆栈到底是存储什么数据的,当然这块内容上文已经讲过,这里就不再赘述了 。
    接下来我们先来聊聊栈是如何垃圾回收的 。其实栈的回收很简单,简单来说就是一个函数 push 进栈,执行完毕以后 pop 出来就当可以回收了 。当然我们往深层了讲深层了讲就是汇编里的东西了,操作 esp 和 ebp 指针,了解下即可 。
    然后就是堆如何回收垃圾了,这部分的话会分为两个空间及多个算法 。
    两个空间分别为新生代和老生代,我们分开来讲每个空间中涉及到的算法 。
    新生代新生代中的对象一般存活时间较短,空间也较小,使用 Scavenge GC 算法 。
    在新生代空间中,内存空间分为两部分,分别为 From 空间和 To 空间 。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的 。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了 。算法会检查 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁 。当复制完成后将 From 空间和 To 空间互换,这样 GC 就结束了 。
    老生代老生代中的对象一般存活时间较长且数量也多,使用了两个算法,分别是标记清除和标记压缩算法 。
    在讲算法前,先来说下什么情况下对象会出现在老生代空间中:
    • 新生代中的对象是否已经经历过一次以上 Scavenge 算法,如果经历过的话,会将对象从新生代空间移到老生代空间中 。
    • To 空间的对象占比大小超过 25 % 。在这种情况下,为了不影响到内存分配,会将对象从新生代空间移到老生代空间中 。
    老生代中的空间很复杂,有如下几个空间
    enum AllocationSpace {// TODO(v8:7464): Actually map this space's memory as read-only.RO_SPACE,// 不变的对象空间NEW_SPACE,// 新生代用于 GC 复制算法的空间OLD_SPACE,// 老生代常驻对象空间CODE_SPACE,// 老生代代码对象空间MAP_SPACE,// 老生代 map 对象LO_SPACE,// 老生代大空间对象NEW_LO_SPACE,// 新生代大空间对象FIRST_SPACE = RO_SPACE,LAST_SPACE = NEW_LO_SPACE,FIRST_GROWABLE_PAGED_SPACE = OLD_SPACE,LAST_GROWABLE_PAGED_SPACE = MAP_SPACE};在老生代中,以下情况会先启动标记清除算法:
    • 某一个空间没有分块的时候
    • 空间中被对象超过一定限制
    • 空间不能保证新生代中的对象移动到老生代中
    在这个阶段中,会遍历堆中所有的对象,然后标记活的对象,在标记完成后,销毁所有没有被标记的对象 。在标记大型对内存时,可能需要几百毫秒才能完成一次标记 。这就会导致一些性能上的问题 。为了解决这个问题,2011 年,V8 从 stop-the-world 标记切换到增量标志 。在增量标记期间,GC 将标记工作分解为更小的模块,可以让 JS 应用逻辑在模块间隙执行一会,从而不至于让应用出现停顿情况 。但在 2018 年,GC 技术又有了一个重大突破,这项技术名为并发标记 。该技术可以让 GC 扫描和标记对象时,同时允许 JS 运行,你可以点击 该博客 详细阅读 。


    推荐阅读