简历提问准备
基础问题
1. 详细介绍一下JS的原型和原型链?什么是原型?什么是原型链?为什么需要有原型链?怎么使用原型链?要注意什么问题?
JavaScript 原型及相关机制详解
1. 原型与原型链基础
1.1 显式原型(prototype)
- 每个函数(构造函数)都有一个
prototype属性,指向一个对象,称为“显式原型”或“原型对象”。 - 箭头函数没有
prototype属性,且不能作为构造函数。
1.2 隐式原型([[Prototype]] / proto)
- 每个对象都有一个隐式原型,通常通过
__proto__访问(标准是内部属性[[Prototype]])。 - 通过构造函数
new创建的实例,其隐式原型指向构造函数的prototype。
1.3 原型链
- 当访问对象属性时,JS 引擎先查找对象自身属性,找不到时沿隐式原型链向上查找,直到找到
Object.prototype,其隐式原型为null。 - 这条链条即为“原型链”,它决定了继承和属性查找机制。
2. 原型存在的意义
- JS 没有传统语言的类型元数据,通过原型实现继承。
- 原型链实现共享方法,减少内存开销。
- 动态修改原型链提供了灵活的继承机制。
3. 使用原型链的方法
- 通过构造函数的
prototype定义共享方法。 - 使用
Object.create(p)创建一个以p为隐式原型的新对象。 - 使用
Object.setPrototypeOf(a, b)动态设置对象a的隐式原型为b(不建议频繁使用,性能开销较大)。
4. 构造函数与原型关系
- 构造函数有
prototype属性,指向显式原型对象。 - 由构造函数
new出来的对象,其隐式原型指向该构造函数的prototype。
5. new 操作符内部执行流程
- 创建一个空对象。
- 设置该对象的隐式原型指向构造函数的
prototype。 - 将构造函数内部的
this指向该对象,执行构造函数代码。 - 返回构造函数返回的对象(若无则返回新创建对象)。
6. instanceof 判断原理
- 判断对象
A的原型链上是否存在构造函数B的prototype。 - 存在则返回
true,否则false。
7. constructor 属性
- 显式原型对象上默认有一个
constructor属性,指向该构造函数。 - 作用:
- 关联实例和构造函数。
- 用于手动恢复继承时丢失的构造函数指针。
- 支持某些场景下动态实例创建。
- 使用
Object.create继承时,原型会丢失constructor,需手动修复。 - 使用
Object.setPrototypeOf修改原型链时,constructor不会丢失。
8. 原型链继承的经典写法与注意
- 不建议用
Child.prototype = new Parent()继承,因会调用构造函数带来副作用并共享引用属性。 - 推荐用
Child.prototype = Object.create(Parent.prototype)干净继承原型。 - 修改已有原型的隐式原型
Object.setPrototypeOf(Child.prototype, Parent.prototype)可以保持constructor,但性能可能受影响。
9. ES6 class 与箭头函数
- 箭头函数不能作为构造函数使用,没有
[[Construct]]。 class是构造函数的语法糖,必须用new调用,且不能直接调用。class中的方法定义在原型上,属性定义在实例上。class代码默认启用严格模式。- 通过
extends实现继承,本质是设置Child.prototype指向Parent.prototype,以及Child.__proto__指向Parent,实现实例继承和静态继承。 - 静态方法定义在构造函数本身,需通过类名调用。
10. super 关键字
super()在子类构造函数中调用父类构造函数,且绑定子类的this。- 必须先调用
super(),否则访问this会报错(实例未初始化)。 super.method()可在子类方法中调用父类方法。- 静态方法中
super调用父类静态方法。
11. new.target
new.target用于判断函数或类是否通过new调用。- 通过
new调用时,new.target指向被调用的构造函数或类。 - 普通调用时,
new.target为undefined。 - 典型用途:
- 确保构造函数必须用
new调用。 - 实现抽象类,防止基类被实例化。
- 确保构造函数必须用
总结
JavaScript 的原型机制及其衍生的 new、instanceof、constructor 等核心特性构成了对象继承的基础;ES6 class 和 super、new.target 等语法糖和新特性在语义上清晰了函数的职责,减少了误用,提高了代码可维护性和开发效率。
2. 介绍一下事件循环?
JavaScript 是一门单线程语言,也就是说,它只有一个主线程来执行所有的代码,包括执行 JS 脚本、处理事件、更新 DOM、样式计算与页面渲染等任务。
为了防止长时间运行的 JavaScript 同步代码阻塞主线程,降低页面的响应速度,JavaScript 引入了异步编程模型。异步任务的执行并不是立刻完成,而是交由宿主环境(如浏览器或 Node.js)处理,待任务完成后再通过事件循环(Event Loop)调度回主线程中执行。
事件循环是一种不断执行的机制,用于调度任务队列中的任务,并按照一定的优先级执行异步回调。每一轮事件循环(称为一个 tick)的执行顺序如下:
从一个宏任务队列中取出一个任务,放入执行栈执行
执行完后立即清空微任务队列中的所有任务
执行渲染(如果需要)
重复以上过程,进入下一轮循环
🧱 微任务(Microtasks)
在当前宏任务执行完之后,立即执行所有微任务,优先级更高。
常见来源:
Promise.then / catch / finally
queueMicrotask()
MutationObserver
微任务队列是全局唯一的。
宏任务(Macrotasks)
包含一个更广泛的任务集合,由浏览器或 Node.js 注册并调度。
每次事件循环 tick 开始时,选择一个宏任务队列的第一个任务来执行。
常见来源:
setTimeout
setInterval
setImmediate(Node.js)
requestAnimationFrame(浏览器渲染前调用)
DOM 事件回调(如点击、输入等)
网络请求回调(如 XMLHttpRequest、fetch)
3. 解释一下执行上下文
一、什么是执行上下文?
执行上下文(Execution Context) 是 JavaScript 代码被解释执行时所处的运行环境。每段代码必须在某个执行上下文中运行。
执行上下文决定了:
- 变量、函数等标识符如何被解析
this的取值- 所属作用域链的构建方式
二、执行上下文的类型
JavaScript 中共有 三种执行上下文类型:
| 类型 | 描述 |
|---|---|
| 全局上下文 | 程序启动时默认创建,this 指向全局对象(浏览器中为 window) |
| 函数上下文 | 每次函数调用都会创建一个新的上下文 |
| Eval 上下文 | eval() 执行的代码会在其独立的上下文中运行(不推荐使用) |
三、执行上下文的组成结构
根据 ECMAScript 规范,执行上下文由以下三部分组成:
LexicalEnvironment(词法环境)
- 存储使用
let、const、函数声明定义的变量 - 包含外层引用(outer environment reference),形成作用域链
- 处理块级作用域(如
if、for等)
- 存储使用
VariableEnvironment(变量环境)
- 存储使用
var声明的变量 - 和词法环境结构相似,在现代引擎中经常合并处理
- 保留用于规范兼容性
- 存储使用
ThisBinding(this 绑定)
- 表示当前执行上下文中的
this值 - 全局上下文中指向全局对象;函数中根据调用方式决定(普通调用、new 调用、箭头函数等)
- 表示当前执行上下文中的
✅ 注意:虽然执行上下文还关联了 Realm(宿主环境)等信息,但这不是其组成部分,仅是附属元数据。
四、执行上下文的生命周期
执行上下文从创建到销毁,通常经历以下三个阶段:
1. 创建阶段
在代码真正执行之前,引擎会完成以下准备工作:
- 确定
this的绑定 - 创建词法环境(注册
let、const、函数声明) - 创建变量环境(注册
var声明) - 建立作用域链
2. 执行阶段
- 开始逐行执行代码
- 完成变量赋值、表达式计算、函数调用等操作
- 如果遇到函数调用,则会创建新的执行上下文并入栈执行
3. 回收阶段
- 当前函数执行完毕后,其上下文从执行栈中弹出
- 若该上下文中定义的变量不再被引用(无闭包),将被垃圾回收器回收
- 全局上下文只有在页面关闭时才会回收
五、执行上下文栈(ECStack)
- JavaScript 引擎使用一个栈结构(执行栈)来管理执行上下文
- 每当一个函数被调用时,会创建其执行上下文并压入栈顶
- 当前正在执行的代码始终位于栈顶的上下文中
- 函数执行完毕后,该上下文从栈顶弹出,控制权回到前一个上下文
示例:
1 | function foo() { |
4. 解释一下JS的垃圾回收机制
首先说什么是垃圾回收,JS是一门高级程序设计语言,不需要像C语言那样使用malloc和free进行手动的内存分配和释放。而是由JS引擎来管理内存分配。释放不需要的对象的内存空间的过程,就是垃圾回收。垃圾回收由JS引擎自动进行,对开发者透明。但是我们也要在编码时注意,避免内存泄漏。
接着来说一下为什么需要垃圾回收,主要是为了降低语言的学习成本和使用难度,这些对内存的操作很容易出现问题,由执行引擎来处理的话,极大地降低了开发者的心智负担。
然后在说JS引擎如何进行垃圾回收。垃圾回收的主要算法有引用计数法和标记清楚法。
引用计数法对每个对象维护一个引用计数器,当引用增加时+1,失效时-1,当计数归零时,进行垃圾回收,释放该对象的内存空间。但是由于无法处理循环引用的问题,现代引擎已经逐步弃用。
标记清除法分为两个阶段。
- 标记阶段:从根对象(全局对象,活动函数调用栈等)出发,递归遍历所有可达对象并标记为“存活”。
- 清除阶段:遍历堆内存,释放未被标记的对象(即不可达对象)占用的内存
通过这种机制,解决了循环引用的问题,无论循环引用多少次,只要全局不可达,就会释放其对应的内存。
最后说一下在基础算法的基础上,V8引擎对垃圾回收的进一步优化。
V8引擎采用复合策略来提升GC的效率。
分代回收(Generational Collection)
- 新生代(Young Generation):存放短生命周期对象(如临时变量),使用
Scavenge算法(复制算法)- 将内存分为
From-Space和To-Space,新对象存入From-Space - 当
From-Space满时,存活对象被复制到To-Space并整理,然后角色互换。
- 将内存分为
- 老生代(Old Generation): 存放长生命周期对象,采用标记-清除和标记-整理结合
- 标记阶段后,将存活对象向内存一端移动,消化碎片内存空间。
- 新生代(Young Generation):存放短生命周期对象(如临时变量),使用
增量标记(Incremental Marking)
将标记过程拆分为子任务,与JS主线程交替执行,避免GC占用时间过长。
- 空闲时间回收(Idle-Time GC)
利用程序空闲时间段执行GC任务,进一步降低对性能的影响。
5. 详细解释一下HTTP/HTTPS协议。
HTTP协议
- 基本原理
- 定义:HTTP是一种无状态的应用层协议,基于C/S模型,通过TCP/IP传输数据(默认端口80)
- 工作流程
- 客户端(浏览器)发起TCP连接(三次握手)
- 发送HTTP请求(包含方法,URL,头部等)
- 服务器处理请求并返回响应(状态码,头部,数据)
- 关闭连接(HTTP/1.1默认支持持久连接,可复用TCP链路)
- 核心组件
- 请求报文
- 请求行:方法(GET/POST等),资源路径,协议版本,如(GET /index.html HTTP/1.1)
- 请求头:元数据(如Host, User-Agent, Accept)
- 请求体:POST/PUT等方法携带的数据(如表单,文件)
- 响应报文
- 状态行:状态码(如 200 OK), 协议版本
- 响应头:元数据(如Content-Type, Content-Length)
- 响应体:返回的实际数据(HTML, JSON)
- 特性与局限
- 无状态性:每次请求独立,需要依赖Cookie/Session管理状态
- 性能问题:HTPP/1.1存在队头阻塞(逐个处理请求),影响效率
- 安全性问题:铭文传输容易被窃听和篡改
HTTPS协议
- 核心原理
- 定义:HTTPS = HTTP + SSl/TLS加密层,默认端口443
- 加密流程:
- 握手阶段:通过非对称加密验证身份并协商密钥
- 数据传输:使用对称加密传输业务数据
- 证书机制:服务器需要部署数字证书(由CA签发),包含公钥,域名等信息,客户端验证证书有效性以防止中间人攻击。
- SSL/TLS握手过程
- Client Hello:客户端支持的加密套件列表 + 随机数
- Server Hello:服务器选择加密套件 + 证书 + 随机数
- 密钥交换:客户端用服务器公钥加密预主密钥,发送至服务器
- 生成会话密钥:双方基于随机数生成对称密钥,用于后续加密通信
6. 详细介绍一下CJS/ESM的区别
- 性质上
- 语法上
- 执行机制
- 动态导入支持上
- 导出值类型上
- 循环依赖处理
- 运行环境
- 互操作最佳实践
7.详细介绍一下pnpm及其原理
pnpm是Performant npm的缩写,是一个兼容npm和yarn的包管理器,其主要目标是:
- 更快的安装速度
- 更少的磁盘占用
- 更好的依赖隔离
pnpm的核心优势
| 特性 | 说明 |
|---|---|
| 节省磁盘空间 | 相同版本的包只下载一次并在项目中共享 |
| 更快的安装速度 | 使用硬链接机制,无需重复复制文件 |
| 严格的依赖隔离 | 默认禁止访问未声明的依赖,避免幽灵依赖的问题 |
| 兼容性强 | 支持npm registry,大部分npm/yarn命令格式 |
pnpm的工作原理
- 内容寻址的全局存储(Content-addressable storage)
- 所有下载的依赖包会存储在全局目录中,默认位置是
~/.pnpm-store - 存储路径不是按包名命名,而是通过其内容生成的哈希值命名(即内容寻址)
- 即使多个项目依赖同一个版本的包,也只会在硬盘上下载并存储一次。
- 所有下载的依赖包会存储在全局目录中,默认位置是
- 使用硬链接(hard link)机制
- 在项目
node_modules目录中不会复制真实的包文件,而是创建指向全局存储的硬链接. - 硬链接是操作系统级别的指针,占用几乎为0,不回重复占用磁盘空间。
- 在项目
8. 详细解释一Vite及其原理
Vite是一种基于现代浏览器的前端构建工具,其核心原理在于利用浏览器原生ES模块实现按需编译,并通过依赖预构建和高效的热更新机制显著提升开发体验。
- 核心设计:基于原生ESM的开发模式
- 按需编译:Vite在开发环境中不预先打包整个应用。当浏览器请求模块(如
import App from './App.vue'时),Vite服务器才动态编译该模块并返回浏览器可以执行的ESM代码。这种机制避免了传统工具的全局打包过程,实现毫秒级冷启动。 - 路径重写与模块解析:浏览器无法直接识别裸模块导入(如
import vue from 'vue')。Vite拦截此类请求,将路径重写为/@modules/vue,在映射到node_modules中的预构建依赖,确保浏览器能正确加载。
- 依赖预构建:性能优化的核心
- 目的与流程:vite启动时使用esbuild(Go语言编写,速度比JS工具块10-100倍)预构建第三方依赖(如
React,Lodash)- 扫描依赖:分析项目中的
import语句,识别需预构建的node_modules包 - 转换与合并:将CommonJS/UMD模块转换为ESM格式,并合并多个小文件以减少HTTP请求数。
- 缓存机制:预构建结构存储在
node_modules/.vite目录,后序启动时直接复用(除非package.json或锁文件变更)
- 扫描依赖:分析项目中的
- 优势:解决模块兼容性问题
- 减少浏览器并发请求压力,提升加载速度
- 按需编译与请求处理流程
- 请求链路示例:
- 浏览器请求
index.html,加载入口脚本<script type="module" src="/src/main.js">。 - 入口文件中的
import语句触发浏览器发起新的请求(如App.vue) - Vite拦截请求,编译
.vue文件为JS代码,再返回给浏览器
- 浏览器请求
- 文件类型处理:非JJS文件,通过插件链实时编译为ESM,例如:
.vue文件被拆解为模板,脚本和样式,分多次请求编译- `
- 热更新机制(HMR)
- 基于WebSocket的实时通信: Vite启动WebSocket服务,监听文件变化,当文件修改时,服务器仅重新编译该模块,并通过WebSocket通知客户端更新。
- 精准更新策略:客户端根据模块依赖图替换修改的模块,保留应用状态(如Vue组件的局部状态),无需刷新页面,例如修改单个Vue组件时,仅该组件的渲染函数更新。
- 性能优势:传统项目的热更新速度随项目规模下降,而Vite的更新耗时与模块数量无关,通常低于100ms。
- 生成环境: Rollup优化
- 切换打包工具:开发环境按需编译不适合生产环境(大量小文件导致网络延迟)。Vite使用Rollup打包生成代码,利用其成熟的
Tree Shaking,代码分割和压缩能力,生成高性能静态资源。 - 一致性配置:Vite的插件系统兼容Rollup,简化了开发与生产环境的一致性配置。
9.说一下React的核心机制及其原理
1 | 状态变化(State)/ 事件触发(Event) |
React的核心机制分为5大部分
- 组件机制(Component System)
React的基础单位是组件,分为:
- 函数组件(Function Component): 使用Hooks管理状态与副作用
- 类组件(Class Component): 通过
this.state和生命周期函数控制行为(已不推荐)
组件的本质:就是一个函数,输入props,输出UI结构虚拟DOM
- 虚拟DOM(Virtual DOM)
- React不直接操作浏览器DOM,而是构建一个内存中的JavaScript对象树。
- 每次渲染组件时,React会生成新的虚拟DOM树,并与旧树做对比,找出变化。
- 然后只更新真正变化的部分到真实DOM(提升性能)
- 状态驱动+声明式UI
React的理念是:UI = f(state)
当状态(state或props)发生变化时,React会自动重新执行组件函数并更新UI,无需手动操作DOM
- Fiber架构:React的渲染引擎
React16+引入的核心调度系统。
Fiber是什么?
- 是一个数据结构,表示组件树中每一个节点的信息(虚拟DOM的增强版)
- 每个组件都会生成一个对应的Fiber节点
- 支持中断渲染,优先级调度,异步渲染等高级特性。
协调阶段
1 | React 会遍历 Fiber 树,构建新的虚拟 DOM |
提交阶段1
2React 把 Effect List 中的变更真正作用到浏览器 DOM
不可中断,必须同步完成
- Diff算法(Reconciliation)
虚拟DOM比较新旧树之间的差异:
- 对比type是否相同(不同则卸载组件,挂载新组件)
- key机制提升列表节点的diff精度(避免重复卸载/重建)
- 避免复杂的全树比较
- Hooks系统
useState:管理局部状态useEffect:副作用处理(如定时器,订阅,DOM操作)useContext:跨组件共享状态useMemo/useCallback:性能优化
- Context + Provider
- 实现跨组件通信
- 避免props层层传递
- Fiber数据结构的详细设计
Fiber的核心是一个链表化的数据结构,用于描述和调度组件的更新过程。Fiber是React的工作单元(unit of work),每一个组件在运行时都会对应一个Fiber节点。


