【面试题】2024前端面试真题之JS篇

《古兰经》中有一句很契合的话,山不过来,我就过去

既然,外部环境我们无法去改变,那就从我们内部改变。所以,我又重新总结了一套,2023年最新的面试集锦,以便大家一起度过寒冬,拥抱更好的未来。

note:

  • 其中有些知识点,在前面的文章中,有过涉猎,为了行文的方便和资料的完整性,我就又拿来主义了,免去大家去翻找。但是,前面的文章有更深的解读,如果想更深的学习,可以移步到对应文章中。
  • 如果在行文中,有技术披露和考虑不周的地方,不吝赐教。
你能所学到的知识点
  1. JS执行流程 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  2. 基本数据类型 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  3. ES6的新特性有哪些 推荐阅读指数⭐️⭐️⭐️
  4. 箭头函数和普通函数的区别 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  5. Promise VS async/await 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  6. ES6迭代器 推荐阅读指数⭐️⭐️⭐️
  7. 设计模式的分类 推荐阅读指数⭐️⭐️⭐️⭐️
  8. WebGL和canvas的关系 推荐阅读指数⭐️⭐️
  9. CommonJS和ES6 Module的区别 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  10. 声明变量的方式 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  11. Object/Map/WeakMap的区别 推荐阅读指数⭐️⭐️⭐️⭐️
  12. JS 深浅复制 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  13. 闭包 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  14. Event Loop 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  15. 垃圾回收机制 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  16. 内存问题 推荐阅读指数⭐️⭐️⭐️
  17. 作用域的产生 推荐阅读指数⭐️⭐️⭐️⭐️
  18. this指向 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  19. 图片懒加载 推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  20. PromiseQueue 推荐阅读指数⭐️⭐️⭐️⭐️
  21. 数组常用方法 推荐阅读指数⭐️⭐️⭐️⭐️

好了,天不早了,干点正事哇。

JS执行流程

准备工作

需要准备执行 JS 时所需要的一些基础环境

  • 初始化了内存中的堆和栈结构
  • JS全局执行上下文
    • 包含了执行过程中的全局信息, 比如一些内置函数全局变量等信息
  • 全局作用域
    • 包含了一些全局变量, 在执行过程中的数据都需要存放在内存中
  • 初始化消息循环系统
    • 消息驱动器
    • 消息队列

执行流程

  1. V8 接收到要执行的 JS 源代码
    • 源代码对 V8 来说只是一堆字符串V8 并不能直接理解这段字符串的含义
  2. V8结构化这段字符串,生成了{抽象语法树|AST},同时还会生成相关的作用域
  3. 生成字节码(介于 AST 和机器代码的中间代码)
    • 与特定类型的机器代码无关
  4. 解释器(ignition),按照顺序解释执行字节码,并输出执行结果。

从图中得出一个结论:执行JS代码核心流程

  1. 先编译
  2. 后执行

通过V8js转换为字节码然后经过解释器执行输出结果的方式执行JS,有一个弊端就是,如果在浏览器中再次打开相同的页面,当页面中的 JavaScript 文件没有被修改,再次编译之后的二进制代码也会保持不变,意味着编译这一步浪费了 CPU 资源

为了,更好的利用CPU资源,V8采用JIT(Just In Time)技术提升效率:而是混合编译执行和解释执行这两种手段

  1. 解释执行的启动速度快,但是执行时的速度慢
  2. 编译执行的启动速度慢,但是执行时的速度快
Just-in-time 编译器:综合了解释器和编译器的优点

为了解决解释器的低效问题,后来的浏览器把编译器也引入进来,形成混合模式

在 JavaScript 引擎中增加一个监视器(也叫分析器)。监视器监控着代码的运行情况,记录代码一共运行了多少次、如何运行的等信息

如果同一行代码运行了几次,这个代码段就被标记成了 warm,如果运行了很多次,则被标记成 hot


基线编译器

如果一段代码变成了 warm,那么 JIT 就把它送到编译器去编译,并且把编译结果存储起来

代码段的每一行都会被编译成一个“桩”(stub),同时给这个桩分配一个以行号 + 变量类型的索引。如果监视器监视到了执行同样的代码和同样的变量类型,那么就直接把这个已编译的版本 push 出来给浏览器。


优化编译器

如果一个代码段变得 very hot监视器会把它发送到优化编译器中。生成一个更快速和高效的代码版本出来,并且存储之

为了生成一个更快速的代码版本,优化编译器必须做一些假设

例如,它会假设由同一个构造函数生成的实例都有相同的形状

就是说所有的实例

  • 都有相同的属性名
  • 并且都以同样的顺序初始化

那么就可以针对这一模式进行优化。

整个优化器起作用的链条是这样的

  1. 监视器从他所监视代码的执行情况做出自己的判断
  2. 接下来把它所整理的信息传递给优化器进行优化
  3. 如果某个循环中先前每次迭代的对象都有相同的形状,那么就可以认为它以后迭代的对象的形状都是相同的。

可是对于 JavaScript 从来就没有保证这么一说,前 99 个对象保持着形状,可能第 100 个就少了某个属性。

正是由于这样的情况,所以编译代码需要在运行之前检查其假设是不是合理的

  • 如果合理,那么优化的编译代码会运行
  • 如果不合理,那么 JIT 会认为做了一个错误的假设,并且把优化代码丢掉
    • 这时(发生优化代码丢弃的情况)执行过程将会回到解释器或者基线编译器,这一过程叫做去优化
{类型特化|Type specialization}

优化编译器最成功一个特点叫做类型特化

JavaScript 所使用的动态类型体系在运行时需要进行额外的解释工作,例如下面代码:

function arraySum(arr) {
  var sum = 0;
  for (var i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
}

我们假设 arr 是一个有 100 个整数的数组。当代码被标记为 “warm” 时,基线编译器就为函数中的每一个操作生成一个桩。sum += arr[i] 会有一个相应的桩,并且把里面的 += 操作当成整数加法。

但是,sum 和 arr[i] 两个数并不保证都是整数。因为在 JavaScript 中类型都是动态类型,在接下来的循环当中,arr[i] 很有可能变成了 string 类型。整数加法和字符串连接是完全不同的两个操作,会被编译成不同的机器码

JIT 处理这个问题的方法是编译多基线桩

  • 如果一个代码段是单一形态的(即总是以同一类型被调用),则只生成一个桩。
  • 如果是多形态的(即调用的过程中,类型不断变化),则会为操作所调用的每一个类型组合生成一个桩。

这就是说 JIT 在选择一个桩之前,会进行多分枝选择,类似于决策树,问自己很多问题才会确定最终选择哪个,见下图:


基本数据类型

数据类型分类(7+1)

  1. undefined
  2. null
  3. Boolean
  4. String
  5. Number
  6. Symbol(es6)
  7. BigInt(es2020)
  8. Object
    1. {常规对象|Ordinary Object}
    2. {异质对象|Exotic Object}

存储位置不同

  • (1 - 7) :栈内存 (基本primary数据类型)
  • (8): 堆内存

判断数据类型的方式 (TTIC)

  1. typeof

    • 判断基本数据类型
    • typeof null 特例,返回的是"object"
  2. Object.prototype.toString.call(xx)

    • 判断基本数据类型
    • 实现原理:
      • 若参数(xx)不为 null 或 undefined,则将参数转为对象,再作判断
      • 转为对象后,取得该对象的 [Symbol.toStringTag] 属性值(可能会遍历原型链)作为 tag,然后返回 "[object " + tag + "
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值