深入理解 JS | 青训营笔记

35 阅读5分钟

一、基本概念

诞生

  • Brendan Eich 于1995年开发
  • 借鉴 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 创建 Node.js
  6. 2010年,Isaac 基于 Node.js 写出了 npm
  7. 2015年6月,第六版 ECMAScript 发布 wohugb.gitbooks.io/javascript/…

进程

  • Browser 进程
  • GPU 进程
  • 渲染进程(GUI 进程、JS 线程、事件触发线程、定时器触发线程、网络线程)
  • 插件进程、网络进程……

特点

  • 单线程
  • 动态、弱类型(运行时才确定数据类型,变量在使用前无需声明类型)
  • 面向对象、函数式
  • 解释类语言、JIT(just in time,即时编译)
  • 安全、性能差
  • ……

数据类型

  • 对象(复杂类型)
    • 数组
    • 函数
    • ……
  • 基础类型
    • 字符串
    • undefined
    • 数字
    • null
    • symbol
    • bigInt
    • 布尔
// 这是 javascript 的单行注释。
/*
这是 javascript 的多行注释。
*/

const a = {
	name =: 'a'
}
const b = a
b.name = 'b';

const c = 'c';
let d = c;
d = 'd'

console.log(a, b, c, d) 
// 输出:{ name: 'b' } { name : 'b' } c d
// 复杂类型存放在地址,修改b的同时会修改a


const arr = [1, 2];
arr.push(3);

const str = 'strstrstr';
str.slice(0, 2);
/*
以新的数组对象,返回数组中被选中的元素。
选择从给定的 _start_ 参数开始的元素,并在给定的 _end_ 参数处结束,但不包括。
不会改变原始数组。
*/ 

console.log(arr, str);
// 输出:[ 1, 2, 3 ] strstrstr
// 复杂类型的原始值可改变,基础类型的原始值不可改变

作用域

  • 决定了变量的可访问性和可见性
  • 静态作用域,可预测代码在执行过程中如何查找标识符
  • 分为全局作用域、函数作用域和块级作用域( { } )

变量提升

  • 可理解为变量的声明会被提升到前面优先执行,并设置默认值undefined
  • var有变量提升
  • letconst没有变量提升,提前访问会报错
  • function可以先调用再定义
  • 赋值给变量的函数无法提前调用

二、如何执行

Pasted image 20230423113113.png

  • AST,abstract syntax tree,抽象语法树,源代码的抽象语法结构的树状表示
  • 为什么不把AST直接转换成机器码:代码量小,节约内存开销

执行上下文

JS引擎解析到可执行代码片段(通常是函数调用),会先做一些执行前的准备工作,这个准备工作称作 执行上下文(execution context,EC),也叫执行环境

  • 全局执行上下文:代码开始执行时创建,压执行栈栈底,每个生命周期内只有一份
  • 函数执行上下文:执行一个函数时,函数内代码会被编译,生成变量环境、词法环境等,当函数执行结束时该执行环境从栈底弹出
  • Eval执行上下文

创建执行上下文

  • 绑定 this

  • 创建词法环境

  • 创建变量环境

  • 词法环境:基于 ECMAScript 代码的词法嵌套结构来定义标识符和具体变量和函数的关联。一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成

  • 变量环境:变量环境和词法环境的一个不同就是前者被用来存储函数声明和变量(letconst)绑定,而后者只用来存储var变量绑定

  • Outer:指向外部变量环境的一个指针

三、进阶知识

闭包

定义一个返回另一个函数的函数 -> 将这个函数赋予一个变量 -> 变量可以访问函数内的变量,函数在执行上下文后产生一个闭包,存放在内存中,函数内的变量没有被栈弹出

this

  • (嵌套)普通函数的this指向 window
  • 对象调用this时指向对象,先赋值再调用,看调用的地方
  • apply方法访问其他对象
  • 一个new函数赋予一个对象
    1. 创建临时对象
    2. this指向临时对象
    3. 执行构造函数
    4. 返回临时对象

垃圾回收

新生代空间 & 老生代空间(副垃圾回收器 & 主垃圾回收器)

  • 新生代 存储小变量,老生代 存储大变量
  • 新生代 的垃圾回收:划分为两个空间:对象区域和空闲区域,对象区域放置活跃的对象和变量,区域满后:垃圾标记 -> 活跃数据移动到空闲区域(对象复制) -> 区域反转
  • 新生代 经过上述两轮垃圾回收后,活跃数据移动到 老生代
  • 老生代 的垃圾回收:垃圾标记 -> 垃圾清除 -> 出现空白,内存不连续 -> 内存整理
  • 垃圾回收时JS为停顿状态,老生代 进行垃圾回收时耗时久,因此将任务分成碎片处理

事件循环

  • JS是单线程,只有一个进程执行JS
  • 两个任务队列:微任务和宏任务
  • 一个专门的计时线程
  • 计时结束时将任务添加到宏任务
  • 微任务队列先于宏任务队列

总结

  1. JS是单线程的,但是Renderi进程里面有多个线程
  2. JS线程和GU线程互斥,执行大的计算任务会导致页面卡顿
  3. 基础数据类型存在栈上,复杂数据类型存在堆上
  4. constlet没有变量提升,提前使用会报错
  5. JS也有编译的过程,执行之前会生成执行上下文(不是纯粹的解释型语言)
  6. 一个执行上下文包括变量环境、词法环境、this
  7. 变量环境里面有一个指向外部函数执行上下文的指针,形成了作用域链
  8. 全局执行上下文只有一份
  9. this和执行上下文绑定