《浏览器工作原理与实践》学习笔记(三)

本文是极客时间上《浏览器工作原理与实践》课程的学习笔记。

栈空间和堆空间

如果想学好前端,那么就必须要搞清楚 JavaScript 的内存机制。

JavaScript 是什么类型的语言

我们把使用之前需要确认其变量数据类型的称为静态语言。
我们把运行过程中需要检查数据类型的语言称为动态语言。
而 JavaScript 就是一种动态语言。

我们把支持隐式类型转换的语言称为弱类型语言。
我们把不支持隐式类型转换的语言称为强类型语言。
所以 JavaScript 是弱类型语言。

JavaScript 变量的类型是可变的,判断变量类型,可以通过 typeof 运算符。这里需要注意运算符对于值为 null 的变量显示的结果也是 object。这是 JavaScript 的历史 bug,需要知道一下。

JavaScript 类型

JavaScript 一共有 8 中类型

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol
  • Object
  • BigInt —— JavaScript 最新的类型,提供了一种方法来表示大于 253 - 1 的整数。

其中 Object 是以键值对的形式出现的。而且对象类型被称为引用类型,而其他 7 种类型被称为原始类型。因为它们在内存中存放的位置是不一样的。

引用类型存在堆中,原始类型存在栈中。

内存空间

JavaScript 执行过程中一共有三种类型的内存

  • 代码空间:存储可执行代码的。
  • 栈空间:它就是调用栈。
  • 堆空间:一个更大的存储数据空间。

原始类型的数据都是保存在栈中的,引用类型的数据都是保存在堆中的。

赋值过程

当赋值行为是原始类型时,会在调用栈中进行变量的创建和赋值操作。

当赋值行为是引用类型时,会将数据分配到堆空间里面,分配后该对象会有一个堆中的地址,然后在将该数据地址赋值给栈中的变量。

所以,原始类型赋值的是数据,而引用类型赋值的只是一个引用地址。

为什么要分堆栈

因为栈除了保存变量,还需要处理程序执行期间的上下文状态。不宜过大,影响性能。
而堆空间很大,能存放很多大的数据。
原始类型的赋值会完整赋值变量值,而引用类型的赋值是复制引用地址。

闭包也是存在于堆空间的

  • 在代码编译过程中,如果 JavaScript 引擎判断函数中形成了闭包,会在堆空间创建一个 closure(foo) 的对象,用来保存闭包所需的变量。
  • 当外部函数执行完毕后,外部函数执行上下文被销毁,但是闭包内的变量还保存在堆中。

简单来说,产生闭包的核心有两步:

  1. 需要扫描内部函数,判断是否产生闭包。
  2. 把内部函数引用的外部变量保存到堆中 closure(foo)

查看堆内存的方法

你可以:
1:打开“开发者工具”
2:在控制台执行上述代码
3:然后选择“Memory”标签,点击"take snapshot" 获取V8的堆内存快照。
4:然后“command+f"(mac) 或者 "ctrl+f"(win),搜索“setName”,然后你就会发现setName对象下面包含了 raw_outer_scope_info_or_feedback_metadata,对闭包的引用数据就在这里面。

关于深拷贝

  1. lodash 的 deepClone。
  2. JSON.parse(JSON.stringify(obj)) 但是这个不适用于有函数的。
  3. 递归遍历对象,这个还不如用 lodash
  4. Object.assign() 方法可以拷贝一层。

垃圾回收

通常情况下,垃圾数据回收分为手动回收和自动回收两种策略。

如 C/C++ 就是使用手动回收策略。
如 JavaScript、Java、Python 就是自动回收策略,产生的垃圾数据是由垃圾回收器来释放的。

调用栈中的数据时如何回收的

首先知道下记录当前执行状态的指针(称为 ESP)

当执行函数时,比如函数1中调用函数2,那么函数1会先被压入调用栈,然后是函数2入栈。这时候 ESP 是指向函数2的。当函数2执行完成后,ESP 就下移指向了函数1,而这个下移操作就是销毁函数2的执行上下文的过程。

当 ESP 下移到函数1时,虽然函数2还在调用栈中,但是它已经是无效(垃圾)内存了。当函数1中再调用函数3时,函数2的内存就会被直接覆盖掉,用来存放另外一个函数的执行上下文。

所以说,JavaScript 引擎会通过向下移动 ESP 来销毁函数保存在栈中的执行上下文。

堆中的数据时如何回收的

要回收堆中的垃圾数据,就需要用到 JavaScript 中的垃圾回收器了。

代际假说

  • 大部分对象在内存中存在的时间很短,简单来说就是很多对象一经分配内存,很快就变得不可访问。
  • 不死的对象,会获得更久。

垃圾回收算法有很多种,各有优劣。

所以,在 V8 中会把堆分为新生代老生代两个区域。新生代中存放是生存时间短的对象,老生代中存放的是生存时间久的对象。

新生代通常只支持 1-8M 的容量,而老生代的容量就大很多。对于两代区域,V8 提供了两个不同的垃圾回收器,以便更高效的实施垃圾回收。

  • 副垃圾回收器,主要负责新生代的垃圾回收。
  • 主垃圾回收器,主要负责老生代的垃圾回收。

垃圾回收器的工作流程

不论什么类型的垃圾回收期,它们都有一套共同的执行流程。

  1. 标记空间中活动对象和非活动对象。
  2. 回收非活动对象所占据的内存。
  3. 内存管理。

副垃圾回收器

分为对象区域和空闲区域,新加入的对象都会被存放到对象区域。当对象区域快写满时,进行垃圾清理操作。
首先对对象区域内的垃圾做标记,标记完成后,把非垃圾的存活的对象复制到空闲区域中有序排列起来。完成复制后对象区域和空闲区域的角色反转。
这种角色反转的操作还能让新生代中的两块区域无限重复使用下去。

对象晋升策略

由于新生代区域空间不大,所以当经过两次垃圾回收依然存活的对象,会被移动到老生代区域中。

主垃圾回收期

特点

  • 对象占用空间大
  • 对象存活时间长

主垃圾回收期是采用标记 - 清除 的算法进行垃圾回收的。

标记:对调用栈进行递归遍历,遍历过程中,能到达的元素成为活动对象,没有达到的元素可以判断为垃圾数据。
清除:直接清除掉标记为垃圾数据所占内存的内容。

由于清除过程不像是副垃圾回收器那样整理排序,所以会出现大量不连续的内存碎片。于是产生了另外一种算法 —— 标记-整理

标记:标记出所有活动数据。
整理:将所有数据向一端移动。

全停顿

全停顿:由于 JavaScript 是运行在主线程之上的,一旦执行垃圾回收算法,需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕后再回复脚本执行。

这显然会影响代码运行的速度,新生代的垃圾回收内存小还好说,老生代的往往会有占用大内存的对象,这会引起明显的卡顿。那么如何解决这个问题呢?

答案是使用增量标记算法:将完整的 标记-清除-整理 过程拆分为一个个单独的小任务,并且穿插在 JavaScript 任务中间执行,这样就大大降低了垃圾回收所带来的的延迟卡顿。

如何判断 JavaScript 是否有内存泄漏

通过chrome的Perfomance面板记录页面的活动,然后在页面上进行各种交互操作,过一段时间后(时间越长越好),停止记录,生成统计数据,然后看timeline下部的内存变化趋势图,如果是有规律的周期平稳变化,则不存在内存泄漏,如果整体趋势上涨则说明存在内存泄漏。

编译器和解释器

编译型语言在程序执行之前,需要经过编译器的编译过程,并且编译后会直接保留机器能够读懂的二进制文件,这样之后每次运行程序直接读二进制文件就不需要再进行编译了。如 Java、C。

解释型语言编写的程序,每次运行程序都需要通过解释器对代价进行动态解释和执行。如 Python、JavaScript。

编译过程

编译型语言:词法分析 - 语法分析 - 生成抽象语法树 - 优化代码 - 生成二进制代码 - 执行二进制代码
解释型语言:词法分析 - 语法分析 - 生成抽象语法树 - 生成字节码 - 执行程序

V8 是如何执行一段 JavaScript 代码的?

在 V8 执行 JavaScript 过程中,既有解释器,又有编译器。

  1. 生成抽象语法树(AST)和执行上下文
  2. 生成字节码 —— 使用字节码而不是直接编译成机器码是因为字节码可以减少系统的内存使用。
  3. 执行代码

关于 AST

类似于 HTML 和 DOM 树,JavaScript 属于方便程序员理解的高级语言,而对于机器理解起来就很困难。所以要将高级语言转为抽象语法树。

其实不止是 JavaScript 在使用 AST,很多其他语言也会使用。而且像 babel、eslint 这类常用工具也是通过 AST 来分析语法的。

AST 的解析过程是先对代码进行分词,再解析成 AST。

如果要看 JavaScript 代码的 AST 语法树可以看下这个 https://resources.jointjs.com/demos/javascript-ast 链接。

代码执行

当 JavaScript 从 AST 被解释器转为字节码后,会逐条解释并执行字节码。

热点代码:出现一段代码重复执行多次的代码,那么后台的编译器就会将这段热点代码从字节码编译为更高效的机器码。这样再遇到热点代码直接执行机器语言就好了。

上面的技术被称为“即时编译(JIT)”。

所以说,V8 执行时间越久,执行效率越高。

JavaScript 的性能优化

对于当前的 V8 技术,主要关注以下三点优化内容:

  1. 提升单次脚本的执行速度,避免 JavaScript 的长任务霸占主线程。
  2. 避免大的内联脚本(<script></script>),因为在解析 HTML 的过程中,解析和编译也会占用主线程。
  3. 减少 JavaScript 文件的容量,因为更小的文件会提升下载速度,并且占用更低的内存。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • JavaScript绝对是最火的编程语言之一,一直具有很大的用户群,随着在服务端的使用(NodeJs),更是爆发了...
    不去解释阅读 2,400评论 1 16
  • 所有知识点已整理成app app下载地址 J2EE 部分: 1.Switch能否用string做参数? 在 Jav...
    侯蛋蛋_阅读 2,407评论 1 4
  • 本文是极客时间上《浏览器工作原理与实践》课程的学习笔记。 变量提升 在 JavaScript 中变量的声明和赋值是...
    VioletJack阅读 391评论 0 0
  • 《深入理解Java虚拟机》笔记_第一遍 先取看完这本书(JVM)后必须掌握的部分。 第一部分 走近 Java 从传...
    xiaogmail阅读 5,056评论 1 34
  • 昨天,卷子的分数公布下来了。 我想:我的卷子应该都是满分。卷子发下来了,我一看,唉,我只有英语得上了满分,我又...
    sx李浩轩阅读 147评论 0 2