Write By CS逍遥剑仙 我的主页: www.csxiaoyao.com GitHub: github.com/csxiaoyaojianxian Email: sunjianfeng@csxiaoyao.com
1. JavaScript 的数据类型JavaScript 是弱类型(支持隐式类型转换),动态(运行时类型推断)语言。
JavaScript 的数据类型有 8 种:7 种 原始类型 和 引用类型 (对象)
2. JavaScript 内存空间2.1 内存模型function foo(){ var a = "1" var b = a var c = {name:"1"} var d = c } foo()栈空间通常不会设置太大,存放原始类型的小数据;堆空间很大,存放引用类型的数据,分配和回收内存会占用一定时间。
2.2 闭包对象产生过程function foo() { var myName = "1" let test1 = 1 const test2 = 2 var innerBar = { setName:function(newName){ myName = newName }, getName:function(){ console.log(test1) return myName } } return innerBar } var bar = foo() bar.setName("test") bar.getName()foo 函数执行并创建执行上下文;预扫描 内部函数,编译过程中遇到内部函数如 setName、getName 则对内部函数进行 快速词法扫描,发现引用了外部函数变量如 myName、test1 则判断为闭包,在堆空间创建 closure(foo) 对象(内部对象 JavaScript 无法访问)来存储闭包变量如 myName、test1;未被内部函数引用的变量如 test2 仍旧保存在调用栈中;当 foo 函数退出,clourse(foo) 依然被其内部的 getName 和 setName 方法引用。所以在下次调用 bar.setName 或 bar.getName 时,创建的执行上下文中就包含了 clourse(foo)。3. 自动垃圾回收3.1 调用栈中的数据回收JavaScript 引擎通过向下移动 ESP (记录当前执行状态的指针) 来销毁函数保存在栈中的执行上下文,效率很高。
function foo(){ var a = 1 var b = {name:"test1"} function showName(){ var c = "1" var d = {name:"test2"} } showName() } foo()3.2 堆中的数据回收3.2.1 代际假说和分代收集代际假说: 大部分对象在内存中存在的时间很短 短期不死的对象,会活得更久
堆中的垃圾数据使用 JavaScript 中的垃圾回收器进行回收。V8 把堆分为 新生代 和 老生代 两个区域,新生代中存放生存时间短的对象,老生代中存放生存时间久的对象。新生区通常只支持 1~8M 的容量,使用 副垃圾回收器 进行回收。老生区支持的容量较大,使用 主垃圾回收器 进行回收。
3.2.2 副垃圾回收器新生代中用 Scavenge 算法 (空间对半划分为对象区域和空闲区域) 来处理。
新对象存放到对象区域,当对象区域快满时执行一次垃圾清理,先对对象区域中的垃圾做标记,再将存活的对象有序复制到空闲区域中,相当于完成了内存整理操作。完成复制后两个角色翻转,完成了垃圾清理。
因为复制时间不宜过长,一般新生区空间会设置得比较小,也因此很容易填满,JavaScript 引擎采用了 对象晋升策略 来解决,即经过两次垃圾回收依然存活的对象会被移动到老生区中。
3.2.3 主垃圾回收器老生区中的对象占用空间大、存活时间长,一部分来自新生区中晋升的对象,一部分来自直接分配的大对象。
老生代采用 标记 - 清除 (Mark-Sweep) 算法进行垃圾回收。标记阶段从调用栈根元素开始递归遍历,根据能否到达区分 活动对象 和 垃圾数据。
垃圾清除阶段清除垃圾数据并产生大量不连续的内存碎片。再使用 标记 - 整理 (Mark-Compact) 算法在标记后将所有存活的对象移向一端,再直接清理边界以外的内存空间。
3.2.4 全停顿由于 JavaScript 运行在渲染进程主线程上,执行垃圾回收将导致暂停执行 JavaScript 脚本,即 全停顿 (Stop-The-World)。为减少全停顿,V8 将标记过程分为多个的子标记过程,与 JavaScript 应用逻辑交替进行,直到标记阶段完成,称作 增量标记 (Incremental Marking) 算法。
4. 编译器和解释器4.1 V8 执行 JavaScript 代码总览编译器和解释器的区别。
JavaScript 是解释型语言,V8 执行 JavaScript 代码的流程总览。
4.2 生成抽象语法树(AST)和执行上下文4.2.1 AST 的应用var myName = "sunshine" function foo(){ return 18; } myName = "csxiaoyao" foo()经过 javascript-ast 处理后生成的 AST 结构如下:
AST 是非常重要的一种数据结构,编译器或解释器依赖于 AST,而非源代码。AST 在很多项目中有着广泛的应用,如:
Babel 的工作原理是先将 ES6 源码转换为 AST,再将 ES6 语法的 AST 转换为 ES5 语法的 AST,最后利用 ES5 的 AST 生成 JavaScript 源代码ESLint 将源码转换为 AST,再利用 AST 来检查代码规范化的问题4.2.2 AST 的生成生成 AST 需要经历分词和解析两个阶段。
1. 分词 / 词法分析 (tokenize)
将源码拆解成一个个语法上不可再分、最小的单个字符或字符串 token
2. 解析 / 语法分析 (parse)
将 token 根据语法规则转为 AST
4.3 生成字节码生成 AST 和执行上下文后,解释器 Ignition 会根据 AST 生成字节码,并解释执行字节码。
引入字节码是为了解决起初 V8 直接将 AST 转换为机器码而导致移动设备内存占用高的问题,字节码就是介于 AST 和机器码之间的一种代码,与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行。
4.4 执行代码 & JIT第一次执行的字节码,解释器 Ignition 会逐条解释执行。在执行字节码的过程中,如果发现有热点代码(HotSpot),即一段代码被重复执行多次,后台编译器 TurboFan 会把该段热点字节码编译为高效的机器码,提高后续执行效率。字节码配合解释器和编译器的技术称为 即时编译 (JIT)。
4.5 JavaScript 性能优化策略提升单次脚本的执行速度,避免 JavaScript 的长任务霸占主线程,使得页面快速响应交互;避免大的内联脚本,因为在解析 HTML 的过程中,解析和编译也会占用主线程;减少 JavaScript 文件的容量,提升下载速度,并且占用更低的内存。 ---来自腾讯云社区的---CS逍遥剑仙
微信扫一扫打赏
支付宝扫一扫打赏