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