青训营笔记 | 字节前端初阶训练营8--深入理解 JS | 豆包MarsCode AI刷题

608 阅读6分钟

开始学前端一个月了, 这里的内容只能听懂六七成, 还得感谢61A教的知识很有用.

几乎全部知识点都记了下来, 有的概念用英语标记对我而言更符合直觉一些

其中有很多知识点我是第一次接触, 难免会有浅薄错误

知识点比较零散, 但有一定的逻辑顺序, 注意对照标题和图片阅读效果更佳

BTW如果有青训营同学路过, 欢迎一起交流:)😘😘

Basic Concepts

浏览器进程模型

  • 进程 VS 线程
  • 重点: 渲染进程 Render Thread

image.png

JavaScript 语言特性:

  • 主任务是单线程的 ( GUI 线程与 JS 线程是互斥的, 二者不能同时进行, JS 卡顿例如死循环时会影响 GUI 的渲染)
  • 动态类型语言 (变量类型可变)
  • 面向对象(...) 函数式 (参数-> 结果, 有些副作用, pure function )
  • 解释型, JIT ( Just In Time compilation ) JS也有编译过程, 会在执行之前生成执行上下文
  • 安全 (只在浏览器上运行), 性能稍差 (解释型语言)

JavaScript Data Type:

推荐 JavaScript.info

image.png

  • 原始数据类型直接按传递,
  • "复杂的"数据类型, 例如 Object 类型, 按引用传递, 对应下面的变量环境与堆空间图片
  • 注意 Immutable VS mutable 的数据类型

JavaScript 作用域

决定了变量的可访问性和可见性 JS 是静态作用域, 亦可称之为词法作用域 (注意与词法环境区分)

  • 全局作用域
  • 函数作用域
  • 块级作用域 ( ES6 新增?)

变量提升 (Variable Hoisting)

可以理解为JS引擎提前扫描变量, 并将变量声明提至代码开头, 注意只是声明, 变量本身还是undefined

那么可从中看出JS并不是纯的解释型语言

  • var 有变量提升
  • let, const 无, 提前访问报错
  • 函数声明可以先调用, 再定义 (注意是函数的声明 Declaration, 而不是函数赋值 Assignment ), 叫做函数提升( Function Hoisting )

JS 是怎么执行的?

image.png

  • AST: Abstract Syntax Tree 抽象语法树
  • 字节码: 比机器码代码量少得多, 节约内存开销
  • 优化代码: JIT 识别热代码(多次出现的代码), 并将其转化成机器码存储, 并再次执行时直接调用, 提升执行速度

词法分析与语法分析

image.png 先转成一个个左边的 Token, 进而转成右边的AST

执行上下文 ( Execution Context )

image.png

  • 全局执行上下文: 代码开始执行时创建, 并一直保留在栈底, 每个生命周期只用一份
  • 函数执行上下文: 每执行一个函数时, 函数代码就会编译成相应变量环境和词法环境等, 函数执行结束时该执行环境从栈顶弹出

栈指调用栈

image.png

创建执行上下文时做了什么

  • 绑定this
  • 创建词法环境
  • 创建变量环境

名词解释

词法环境:
  • 基于ECMAScript代码的词法嵌套结构来定义标志符和具体变量和函数的关联, 一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成
  • 简单来说, 就是存储函数和变量绑定( letconst )
变量环境:
  • 变量环境和词法环境的一个不同是前者用来存储函数声明与变量( letconst )绑定, 后者只用来存储 var 变量绑定
  • 简单来说, 就是只存 var 的变量绑定
Outer

指向外部变量环境的一个指针

变量环境的一个图解: 对应上面的按引用传递

基础数据类型存在栈上, 复杂数据类型存在堆上

image.png 注意上面的 "1003" 指的是地址值

调用栈的图解 (调用栈上的垃圾回收)

image.png 当func执行结束时, ESP会转而指向全局执行上下文,此时func执行上下文变为无效内存,

要是有其他函数执行例如func2执行, func2执行上下文会直接覆盖func执行上下文,

即可完成栈上的垃圾回收

JavaScript 进阶知识点

闭包 Closure

image.png 闭包的外函数会创建一个闭包地址, 该地址指向的内存存有需要闭包访问的变量, 并将该地址跟随上图中的 getName 变量, 直到其生命周期结束

那么可知闭包的一个缺点: 闭包内存不容易被回收, 可能会造成内存泄露

this keyword

  • 普通函数中的 this 指向的是 window, 无论是否为嵌套函数
  • 对象 (object) 调用 this 时, 指向的是对象, 默认是对象本身, 可以用 apply, call, bind 等在调用时改变 this 的指向

new keyword

function ShowName() {
	this.name = "Bytedance";
}
const getName = new ShowName();
new 做了什么
  • 创建临时对象
  • this 指向临时对象
  • 执行构造函数
  • 返回临时对象

堆上的垃圾回收

image.png 堆的空间分为新生代空间和老生代空间, 一般不是内存特别大的变量都是放在新生代

对于新生代的垃圾回收

使用副垃圾回收器, 先将新生代空间化成两个区域, 对象区域和空闲区域

对象区域放置活跃的变量与对象, 当对象区域快存满时进行:

  • 垃圾标记: 标记不活跃的数据
  • 对象赋值: 将不活跃的数据复制到空闲区域
  • 区域反转: 反转后将原来的对象区域清空, 即完成一次垃圾回收
对于老生代的垃圾回收

老生代一般是: 占用空间大的数据, 新生代中经过两次垃圾回收仍存在的数据

使用主垃圾回收器

  • 垃圾标记 (上图橙色的块)
  • 清除垃圾
  • 整理内存碎片, 使其变为连续内存

注意垃圾回收时JS 会停顿(全停顿), 但是垃圾回收耗时很大

因此主垃圾回收器会将标记任务分解为多个小任务

JS的事件循环

问: 怎么处理异步任务呢?

答: JS有两个事件队列: 微任务队列, 宏任务队列

当主线程代码执行完了之后, 进行事件循环, 执行宏任务与微任务

注意是微任务队列先于宏任务队列执行, 先把微任务做完, 再去做宏任务

image.png image.png (建议看视频里的代码)当使用setTimeOut函数时, 利用定时器触发线程(最低4ms), 时间到了后立马放入宏任务队列

对于Promise 第一个的函数是同步代码, 但是里面的异步任务会放在事件触发线程里, 而 then 函数是微任务队列里

image.png