JavaScript 总结

JavaScript 大类

我理解分为 运行时 和 编译原理那一套文法、语义

运行时

  1. 就是包括了 JS 的基础数据类型,拆箱转换,装箱转换等,以及基于原型的面向对象思想,继承、原型链等等
  2. 执行过程,事件循环,异步任务,微任务(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)

作用域链

可执行上下文中的词法环境中含有外部词法环境的引用,我们可以通过这个引用获取外部词法环境的变量、声明等,这些引用串联起来一直指向全局的词法环境,因此形成了作用域链。是用来解析标识符的,它是词法作用域的一种实现方式,即外部环境引用。

函数执行过程

  1. 将变量存入内存堆
  2. 创建全局可执行上下文
  3. 创建函数可执行上下文
  4. 开始执行,将上下文中的变量赋值,然后执行函数代码,执行完毕后将对应函数弹出栈,然后完成

闭包

可执行上下文中的词法环境中含有外部词法环境的引用,我们可以通过这个引用获取外部词法环境的变量、声明等,因此形成了闭包。

常用于:

  1. 模拟似有变量(ts 出现之后 private 关键字可以完美替代,es6 的 symbol 也可以模拟私有变量)
  2. 储存变量(会一直存在内存中,垃圾回收不会回收,因此如果滥用会造成内存泄漏)

垃圾回收

特点原则:

  1. 新生对象倾向于被尽快清除
  2. 没被清除的对象会生存的更久

一些过程优化

分代回收机制

【新生代】

对象分为新生代(对象存活时间短)、老生代(对象存活时间长)

  • 内存一分为二,一部分使用中(From 空间),一部分闲置(To 空间)
  • 内存分配给 From 空间,如果满了,执行垃圾回收(全停顿的话会停止应用逻辑)
  • 如果对象存活则判断晋升条件,符合的直接晋升为老生代,不符合从 From 空间复制到 To 空间
  • 对象不存活,则直接释放
  • 完成复制后,From 空间与 To 空间翻转

【老生代】

获取根并标记,标记来自根的引用,然后继续往下标记,直到所有可访问的对象全部被标记。然后未被标记的清楚掉

优化:

  1. 标记整理,上述方案会导致内存碎片化,如果内存空间不够的话,会执行整理过程,现将所有被标记的移到一边,然后再清理
  2. 增量标记,因为老生代存活对象多,垃圾回收时间长,因此将一次标记过程分成很多小步,交替进行:JS->垃圾回收->JS->垃圾回收,这样

编译原理(解析时)

变量提升

先获取所有被声明的变量,然后再一行一行的运行,所有的变量声明语句都会被提升到代码头部(var,和 function 声明的函数)

原因是因为,JavaScript 在浏览器中执行分为两个阶段:解析阶段、执行阶段

然后和 JS 引擎创建执行环境有关,执行环境包含三个:父级执行上下文中的变量、参数/内部变量/函数声明、this,这三个构成了执行环境,创建阶段会扫描内部代码,找到所有函数声明将函数名和函数引用存入变量对象,然后扫描内部代码查找变量声明,对于所有找到的变量声明,会存入变量对象中,并初始化 undefined。因此造成了变量提升的现象。