Web引擎
JavaScriptCore 是 JavaScript 引擎,通常会被叫做虚拟机,专门设计来解释和执行 JavaScript 代码。最开始的 JavaScriptCore 是从 KJS(KDE 的 JavaScript 引擎)以及 PCRE 正则表达式的基础上开发的,是基于抽象语法树的解释器。2008 年重写了,叫做 SquirrelFish,后来是 SquirrelFish Extreme,又叫 Nitro。目前 JavaScript 引擎还有 Google 的 V8 ,Mozilla 的 SpiderMonkey。
核心引擎
JavaScript
源代码会由词法分析器(Lexer
)生成token
流,然后通过语法分析器(Parser
)生成AST
(抽象语法树),生成AST
后使用字节码生成器(ByteCode Generator
)生成字节码(ByteCode
),然后启动解释器(LLInt
)进行解释执行,如果同一行代码运行了几次,则标记为warm
,运行了很多次,则标记为hot
。如果标记为warm
,那么JIT
就会把它送到基线编译器,并把编译结果存储起来,比如,监视器监视到了,某行、某个变量执行同样的代码、使用了同样的变量类型,那么就会把编译后的版本,替换这一行代码的执行,并且存储。如果一个代码段变得hot
,分析器会把它发送到优化编译器中。生成一个更快速和高效的代码版本出来,并且存储。例如:循环加一个对象属性时,假设它是 INT
类型,优先做 INT
类型的判断
类型推断
JavaScript是一门解释型弱类型语言,在生成AST后,一边解释一边执行,当一段代码被多次执行后,这就会有被优化的空间。编译型语言如Java,可以在执行前进行优化编译,但是要花费大量时间,不适合web交互。
实现
- 分析值 d=3+c
- 类型预测 d(int32)=3+c(int32)
- 类型检查 d=3+2=5
- 类型验证 typeof d == number
- 更新到编译结果
对象访问优化
- 字典模式(slow mode)
- 也成为哈希表模式,V8使用哈希表来存储对象的属性
- Stable模式(fast mode)
- 使用类似数组结构来存储对象的属性并使用offset访问
- 新创建的小对象默认是fast mode
- 退化成字典模式
- 动态添加太多属性
- 删除属性
- 进化成快速模式
- RuntimeCall
- 对象被设置为一个函数的原型(Object.create)
事件循环
执行栈
- 执行上下文
- 外部作用域
- 私有作用域
- this
- 同步代码依次加入执行栈
任务队列
- 异步回调函数
- 异步回调上下文
- 宏任务(定时器、IO回调、整体script)
- 微任务(Promise、MutationObserver、process.nextTick、setImmediate)
执行顺序
- 执行执行栈的同步代码->堆积微任务->UI线程->从任务队列获取宏任务并执行->微任务执行
- 执行完一个宏任务就一次性执行玩所有的微任务
JavaScript虚拟机
垃圾回收
- 引用计数,潜在循环引用问题
- 标记清除
- 从根节点开始查找
- 标记所有可以访问对象
- 标记所有不可访问对象
内存泄漏
- 全局变量
- 绑定事件没有解绑
- 定时器没有清除
- 闭包引用
- DOM元素引用,常用WeakSet、WeakMap处理
数据结构
原始类型
- Boolean
- Number
- Infinity
- NaN
- String
- undefined
- null
- Symbol
- Object
内建对象
- Date
- Math
- 数组
- 集合
- Map
- WeakMap
- Set
- WeakSet
- JSON
- RegExp
内存模型
- 栈 按值传递
- 堆 按引用传递
类型检测
- typeof
- null 用===判断
- NaN用!==判断
- instanceof
- Object.prototype.toString.call
克隆
- 深克隆
- JSON.parse
- RegExp、FUNC无法克隆
- 对象原型无法克隆
- JSON.parse
- 浅克隆
- for in
- hasOwnProperty
- Object.assign
数据监听
- Object.observe(废弃)
- Object.defineProperty
- get 依赖收集
- set 派发更新
- 发布订阅模式
- Proxy
- Reflect
- 劫持整个对象
- 可监听数组对象