JavaScript 总结
JavaScript 总结
JavaScript 大类
我理解分为 运行时 和 编译原理那一套文法、语义
运行时
- 就是包括了 JS 的基础数据类型,拆箱转换,装箱转换等,以及基于原型的面向对象思想,继承、原型链等等
- 执行过程,事件循环,异步任务,微任务(JS 发起),宏任务(宿主浏览器发起),代码运行机制(作用域链,闭包,this 指向,垃圾回收)等
解析时(编译原理)
解析 token(词法分析),根据 token 组合成有一定语义的结构(语法分析),预编译等等,比如变量提升现象就是在预编译这里的,这一部分其实比较深了。
JavaScript 类型(运行时 1)
基础类型
七种:
- 原始类型:String, Number, Boolean, Undefined, Null, Symbol,
- 复杂类型:Object
类型转换
类型转换
- 装箱转换:基本类型 -> 对象,Object(target)
- 拆箱转换:对象 -> 基本类型,拆箱转换会通过 valueOf、toString 来获得拆箱后的基本类型
原型链
是用来查找对象属性的,它是原型继承(prototype-based inheritance)的一种实现方式。
- 以 JavaScript 为代表的基于原型的面向对象【详情见内】
- JavaScript 的继承(原型链继承、构造函数继承、Object.create 继承、es6 的继承)【详情见内】
JavaScript 运行机制(运行时 2)
执行过程
V8 引擎主要由两部分组成:内存堆(内存分配地址的地方)、调用栈(代码执行的地方)。
运行时包含执行 JS 代码,完成内存分配,垃圾回收这些过程。
比如执行函数的时候,会把函数压入栈中,执行的过程中有一个很关键的概念,执行上下文,就是根据这个来执行函数的。
- 全局上下文:一个程序只会有一个
- 函数上下文:每当一个函数被调用执行的时候,都会创建一个函数上下文
创建阶段(这部分其实应该算在解析时里):
- this 指向(因此得出 this 指向是在运行时确定的)
- 创建词法环境(let、const 等都在这里面)
- 环境记录器:存储变量、函数声明位置
- 外部环境引用:指向可以访问的父级词法环境(即作用域,作用域链的概念也在这里)
- 创建变量环境(var)
作用域链
可执行上下文中的词法环境中含有外部词法环境的引用,我们可以通过这个引用获取外部词法环境的变量、声明等,这些引用串联起来一直指向全局的词法环境,因此形成了作用域链。是用来解析标识符的,它是词法作用域的一种实现方式,即外部环境引用。
函数执行过程
- 将变量存入内存堆
- 创建全局可执行上下文
- 创建函数可执行上下文
- 开始执行,将上下文中的变量赋值,然后执行函数代码,执行完毕后将对应函数弹出栈,然后完成
闭包
可执行上下文中的词法环境中含有外部词法环境的引用,我们可以通过这个引用获取外部词法环境的变量、声明等,因此形成了闭包。
常用于:
- 模拟似有变量(ts 出现之后 private 关键字可以完美替代,es6 的 symbol 也可以模拟私有变量)
- 储存变量(会一直存在内存中,垃圾回收不会回收,因此如果滥用会造成内存泄漏)
垃圾回收
特点原则:
- 新生对象倾向于被尽快清除
- 没被清除的对象会生存的更久
一些过程优化
分代回收机制
【新生代】
对象分为新生代(对象存活时间短)、老生代(对象存活时间长)
- 内存一分为二,一部分使用中(From 空间),一部分闲置(To 空间)
- 内存分配给 From 空间,如果满了,执行垃圾回收(全停顿的话会停止应用逻辑)
- 如果对象存活则判断晋升条件,符合的直接晋升为老生代,不符合从 From 空间复制到 To 空间
- 对象不存活,则直接释放
- 完成复制后,From 空间与 To 空间翻转
【老生代】
获取根并标记,标记来自根的引用,然后继续往下标记,直到所有可访问的对象全部被标记。然后未被标记的清楚掉
优化:
- 标记整理,上述方案会导致内存碎片化,如果内存空间不够的话,会执行整理过程,现将所有被标记的移到一边,然后再清理
- 增量标记,因为老生代存活对象多,垃圾回收时间长,因此将一次标记过程分成很多小步,交替进行:JS->垃圾回收->JS->垃圾回收,这样
编译原理(解析时)
变量提升
先获取所有被声明的变量,然后再一行一行的运行,所有的变量声明语句都会被提升到代码头部(var,和 function 声明的函数)
原因是因为,JavaScript 在浏览器中执行分为两个阶段:解析阶段、执行阶段
然后和 JS 引擎创建执行环境有关,执行环境包含三个:父级执行上下文中的变量、参数/内部变量/函数声明、this,这三个构成了执行环境,创建阶段会扫描内部代码,找到所有函数声明将函数名和函数引用存入变量对象,然后扫描内部代码查找变量声明,对于所有找到的变量声明,会存入变量对象中,并初始化 undefined。因此造成了变量提升的现象。
- 感谢你的欣赏!