理解JS | 青训营笔记

101 阅读7分钟

JS的诞生与发展

JS于1995年,由Brendan Eich开发

借鉴C语言的基本语法 ; 借鉴Java语言的数据类型和内存管理 ; 借鉴Scheme语言,将函数提升到"第一等公民”(first class)的地位 ; 借鉴Self语言,使用基于原型 (prototype)的继承机制

  1. Mocha>1995.9.LiveScript >1995.12 JavaScript
  2. 1997年6月,第一版ECMAScript发布
  3. 1999年12月,第三版ECMAScript发布
  4. 2009年12月,第五版ECMAScript发布
  5. 2009 年,Ryan 创建了Nodejs
  6. 2010年,Isaac基于node.js写出了npm
  7. 2015年6月,第六版ECMAScript发布

基本概念

数据类型

基础数据类型 Number String Boolean Null Undefined Symbol BigInt

复杂数据类型 Object (数组 函数 ...)、

作用域

全局作用域

表示变量可以在整个程序代码中被访问。如果一个变量不是在任何一个函数内部定义的,那么它就是在全局作用域中定义的。可以使用 var、let 或 const 关键字声明全局变量

函数作用域

表示变量只能在函数内部被访问,这种作用域在函数被执行时才会创建,并在函数执行结束后销毁。可以使用 var、let 或 const 关键字声明函数内部的变量

块级作用域

let 和 const 声明的变量具有块级作用域,仅在其声明的花括号{}内有效。例如:

提升

变量声明(var、let、const):变量声明会被提升到当前作用域的顶部,并被赋值为 undefined(如果是 var 声明的变量)或保持未定义(如果是 let 或 const 声明的变量),直到代码运行到实际声明语句位置。

函数声明:函数声明也会被提升到当前作用域的顶部,可以在声明之前调用函数。

JS是怎么执行的

图片.png

JavaScript 的执行过程可以进一步细分为以下几个步骤:

  1. 词法分析:将代码分解成单独的词法单元,例如关键字、变量名、操作符等。
  2. 语法分析:将词法单元组织成语法结构,例如语句、表达式等。这个过程会生成抽象语法树(AST)。
  3. 代码生成:将 AST 转换成可执行的字节码或机器码。
  4. 执行代码:按顺序执行生成的字节码或机器码。

【 在执行代码的过程中,引擎会维护一个执行上下文栈,每当执行一个函数时,就会创建一个执行上下文,将其压入栈中,待执行完毕后,再将其弹出栈。执行上下文包括函数的作用域、变量、参数等信息。在执行函数时,引擎会从栈顶取出对应的执行上下文,并将其设置为当前执行上下文 】

在创建执行上下文时做了什么

创建变量对象:JavaScript引擎会在进入执行上下文时创建一个变量对象,用于存储在当前上下文中声明的变量和函数。 确定作用域链:JavaScript引擎会在创建变量对象后,根据当前上下文的嵌套关系,确定作用域链。 确定this的值:JavaScript引擎会根据当前函数的调用方式,确定this的值 】

进阶知识

闭包

闭包通常由两部分组成:内部函数和外部函数。当内层函数引用外层函数中的变量时,JavaScript 引擎会创建一个闭包,将这些变量保存起来以备后续使用,并且保证内层函数对这些变量的修改不会影响到外层函数和其他函数对这些变量的访问。

this

  • 方法调用下: this指向调用它所在的方法
  • 函数调用下: this指向window
  • 调用方法没明确的时候: this指向window (setTimeOut 、匿名函数)
  • 构造函数调用下: this指向被构造的实例对象
  • apply call bind 调用下: this指向第一个参数
  • 箭头函数调用下: 在声明时绑定this 而非取决于调用位置 指向它的上一层
  • 严格模式下: 若this没执行环境 (execation cantext)定义 this指向undefined

垃圾回收

image.png

v8对GC(回收垃圾的)的优化

  1. 分代式垃圾回收:v8 的垃圾回收策略主要基于分代式垃圾回收机制,V8 中将堆内存分为新生代和老生代两区域。
  2. 新生代垃圾回收:用了一种复制式的方法即 Cheney算法Cheney算法 将堆内存一分为二,一个是处于使用状态的空间我们暂且称之为 使用区,一个是处于闲置状态的空间我们称之为 空闲区

新加入的对象都会存放到使用区,当使用区快被写满时,就需要执行一次垃圾清理操作,使用区和空闲区互相交换。

  • 生命周期较长的对象或者空闲区空间占用超过了 25% ,那么这个对象会被直接晋升到老生代空间中,原因是当完成 垃圾 回收后,空闲区将翻转成使用区,继续进行对象内存的分配,若占比过大,将会影响后续内存分配。
  • 新生代回收使用并行回收提高效率并行回收依然会阻塞主线程
  1. 老生代垃圾回收
  • 整个流程就采用的就是上文所说的标记整理算法
  • 采用增量标记、惰性清理,并发回收提高效率
  • 增量标记是指将一次垃圾回收的过程分为很多个小部分,每次执行完一小块,就让应用程序执行一会,交替执行。增量标记采用三色标记法(暂停与恢复)与写屏障(增量中修改引用)

惰性清理指的是让js脚本先执行,也无需一次性清理完所有非对象内存。

  1. 并发回收:主线程在执行 JavaScript 的过程中,辅助线程能够在后台完成执行垃圾回收的操作,辅助线程在执行垃圾回收的时候,主线程也可以自由执行而不会被挂起

事件循环

js代码主要分为两大类: 同步代码、异步代码。 异步代码又分为:微任务与宏任务

事件循环Event Loop执行机制

  1. 进入到script标签,就进入到了第一次事件循环
  2. 遇到同步代码,立即执行
  3. 遇到宏任务,放入到宏任务队列里
  4. 遇到微任务,放入到微任务队列里
  5. 执行完所有同步代码
  6. 执行微任务代码
  7. 微任务代码执行完毕,本次队列清空
  8. 寻找下一个宏任务,重复步骤1

以此反复直到清空所有宏任务,这种不断重复的执行机制,就叫做事件循环

  • 宏任务:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
  • 微任务:Promise、MutaionObserver、 process.nextTick(Node.js 环境)

执行过程:在所有同步任务执行完毕之后,异步任务会优先执行所有已经存在任务队列中的微任务。在所有的微任务执行完毕之后,再去宏任务队列中执行一个宏任务,执行完一个宏任务之后会再去微任务队列中检查是否有新的微任务,有则全部执行,再回到宏任务队列执行一个宏任务,以此循环。

总结

  • JS是单线程的,但Render进程里面有多个线程
  • JS线程和Gui线程互斥,执行大量计算任务会导致页面卡顿
  • 基础数据类型存在栈上,复杂数据类型存在堆上
  • let、const没有变量提升,提前使用会报错
  • JS也有编译过程,执行前会生成执行上下文
  • 一个执行上下文包括变量环境,词法环境,this
  • 变量环境里面有一个指向外部函数执行上下文的指针,形成了作用域链
  • 全局上下文只有一份
  • this和执行上下文绑定