🦅面试(day3)
Opened this issue · 8 comments
- 最近做过哪些项目,有哪些优化点(从技术深度,广度展开)
- webpack commonChunk 底层实现原理 (查看答案)
- webpack 如何拆分依赖模块,拆分规则是什么
- webpack 构建产物的底层结构,模块加载原理(查看答案)
- nuxt 架构,底层实现原理,ssr 使用哪些模块实现 (实现原理)
- nodejs 如何实现多进程,如何监听端口号(操作系统层面讲实现原理)
- v8 引擎原理,编译阶段如何进行优化字节码、机器码,什么场景下会执行反优化(查看答案)
- vuex 如何实现响应式的,组件层面如何实现响应式,哪些阶段进行依赖收集,都包含哪些场景,展开讲讲(参考)
- https 执行过程(查看答案)
- http 2.0 做了哪些优化?多路复用的原理?分帧传输如果数据传输异常,会如何处理?(参考答案)
- 设计一个 Form 表单,给出整体架构设计(如何拆分模块,功能点,应用设计模式)多个子模块间如何进行发布订阅,如何统一维护关联子模块与父模块的数据联系(无)
- redux 底层实现原理,说出实现过程(参考答案)
- react fiber,讲讲双缓冲模式(参考答案)
- useState 执行到渲染阶段都经历了哪些过程
- 讲讲 react 生命周期,如何利用生命周期 Hook 进行调优
- reactNative 实现逻辑,底层架构实现方案是什么,如何进行多端磨平适配
算法题
- 找出根节点到叶子节点的最大数字和的路径(使用广度优先算法)
/*
** TreeNode {
** value: number;
** children: TreeNode[] | null
** }
** SingleNode {
** value: number;
** next: SingleNode | null
** }
*/
function findPath(tree: TreeNode): SingleNode {}
- 二维数组中,“1”相连的最大数量
const grid = [
[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]
]
function maxAreaCount(grid) {
};
Q: webpack commonChunk 底层实现原理
A: webpack 在 make 阶段
进行模块的依赖收集,生成一个 dependency graph
,并确定模块之间的依赖关系。然后,在遍历图,根据依赖引用的次数,如果达到阈值就生成一个新的 chunk,然后将原来的依赖图的 chunk 进行删除,并创建一个引用指向新的 chunk 。同时,webpack 会生成一个 manifest.json 文件,用来管理和映射资源文件之间的依赖关系。
// Webpack manifest
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
["manifest"],{
// 模块标识符和对应的模块函数
"moduleA": function(module, exports, require) {
// 模块A的代码...
},
"moduleB": function(module, exports, require) {
// 模块B的代码...
},
// 更多模块...
// Chunk 映射和加载逻辑
"0": [
"moduleA", "moduleB" // 表示 chunk 0 包含模块A和模块B
],
// 更多 chunk 映射...
}
]);
Q: webpack 如何拆分依赖模块,拆分规则是什么?
A: 首先根据entry 配置生成依赖图,并确定模块之间的依赖关系。遍历依赖图,根据配置规则来决定哪些共享模块是需要拆分进行分组。然后确定模块之间的加载顺序,并删除重复模块。最后根据优化后的依赖图输出打包静态资源。
Q: nodejs 如何实现多进程,如何监听端口号
A: Node.js 是基于单线程的事件循环模型设计的,这意味着它在默认情况下不会利用多核 CPU 的全部能力。然而,Node.js 提供了一些模块和机制来创建和管理多个进程,从而实现并行执行和负载均衡。主要的方法包括使用 child_process 模块和 cluster 模块。
使用 child_process 模块
child_process 模块允许 Node.js 运行一个子进程,并与之通信。可以使用它来执行一个系统命令、调用另一个 Node.js 脚本或任何其他程序。
这个模块提供了几种创建子进程的方法,包括:
- exec():用于执行命令,缓冲每个子进程的输出,并将完整的输出作为回调函数的参数返回。
- spawn():用于启动一个新进程来运行命令。它返回一个带有 stdout 和 stderr 流的对象,你可以使用这些流来读取子进程的输出。
- fork():是 spawn() 的特殊形式,专门用于创建新的 Node.js 进程。与 spawn() 不同的是,它允许父进程和子进程之间通过 IPC(进程间通信)通信。
使用 cluster 模块
cluster 模块允许你轻松地创建共享服务器端口的子进程。这对于在多核 CPU 上运行的 Node.js 应用特别有用,因为它可以帮助你利用所有的 CPU 核心。基本上,cluster 模块允许你启动一个主进程和若干个工作进程(worker processes),主进程可以监控和控制工作进程。
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length; // 获取 CPU 的核心数
if (cluster.isMaster) {
console.log(`主进程 ${process.pid} 正在运行`);
// 衍生工作进程。
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 已退出`);
});
} else {
// 工作进程可以共享任何 TCP 连接。
// 在本例子中,它是一个 HTTP 服务器
http.createServer((req, res) => {
res.writeHead(200);
res.end('你好世界\n');
}).listen(8000);
console.log(`工作进程 ${process.pid} 已启动`);
}
Node.js 中使用 child_process 模块创建的子进程是独立的进程。当你在 Node.js 应用中创建一个子进程时(无论是通过 exec(), spawn(), fork() 或 execFile() 方法),Node.js 会在操作系统层面启动一个新的进程。这个新启动的进程与启动它的父进程(即你的 Node.js 应用的进程)是独立的,它们在操作系统中拥有不同的进程 ID(PID)。
Q: 设计一个 Form 表单,给出整体架构设计(如何拆分模块,功能点,应用设计模式)多个子模块间如何进行发布订阅,如何统一维护关联子模块与父模块的数据联系
A:
1. 模块化设计
将表单拆分为多个模块,每个模块负责一个具体的功能,例如:
- Form Manager:负责管理整个表单的状态,包括数据收集、验证、提交等。
- Field Component:每种类型的输入字段(如文本框、选择器、复选框等)都应该有自己的组件,以便重用。
- Validation Module:负责字段验证逻辑。
- Data Binding Module:负责管理表单数据和UI之间的双向绑定。
- Event Bus:用于模块间的通信。
2. 功能点
- 动态字段:表单应该能够根据需求动态添加或移除字段。
- 数据验证:支持各种验证规则,并能给出用户友好的提示。
- 数据收集和提交:收集表单数据并支持异步提交。
- 状态管理:跟踪表单的状态(如是否被修改、是否正在提交等)。
3. 设计模式
- 观察者模式(发布订阅模式):用于模块间的通信,如字段值变化、表单提交等事件。
- 工厂模式:用于创建不同类型的字段组件。
- 策略模式:用于实现不同的验证规则。
4. 发布订阅模式的应用
Event Bus 或 EventEmitter 可用于实现发布订阅模式,使得子模块(字段组件、验证模块等)能够订阅表单管理器发布的事件,并在适当的时候发布自己的事件。例如,一个字段值的变化可以发布一个事件,表单管理器订阅这个事件来更新表单的总体状态。
5. 数据联系的维护
- 单一真实数据源:Form Manager作为单一真实数据源,存储所有字段的当前值。
- 双向数据绑定:字段组件通过Data Binding Module与Form Manager双向绑定,确保UI和数据的一致性。
- 状态提升:将子模块(字段组件)的状态提升到父模块(Form Manager),由父模块统一管理。
Form System
│
├── Form Manager
│ ├── 状态管理(字段值、验证状态、表单状态)
│ ├── 数据收集和提交
│ └── 事件发布(字段变化、表单提交等)
│
├── Field Components
│ ├── 文本框、选择器、复选框等
│ └── 数据绑定和事件订阅
│
├── Validation Module
│ ├── 规则定义
│ └── 验证执行
│
└── Event Bus
└── 模块间通信
Q: 补充完善Person类
const p = new Person();
p.eat().wait(1000).eat().eat().wait(1000).eat()
A:
class Person {
promise: Promise<void>
constructor() {
this.promise = Promise.resolve()
}
eat() {
this.promise.then(() => {
console.log('eat')
return Promise.resolve()
})
return this;
}
wait(delay) {
this.promise = this.promise.then(() => {
return new Promise(resolve => setTimeout(resolve, delay));
})
return this;
}
}
const p = new Person();
p.eat().wait(1000).eat().eat().wait(1000).eat()
// 输出: eat -> 延迟1s -> eat
/*
** 找出根节点到叶子节点的最大数字和的路径(使用广度优先算法)
** TreeNode {
** value: number;
** children: TreeNode[] | null
** }
** SingleNode {
** value: number;
** next: SingleNode | null
** }
*/
/**
* (1)
* (2),(3),(5)
* (4,5)(6,7),(4,6,7)
*/
class TreeNode {
value: number
children: TreeNode[] | null
constructor(value: number, children: TreeNode[]) {
this.value = value
this.children = children
}
}
class SingleNode {
value: number
next: SingleNode | null
constructor(value: number) {
this.value = value
this.next = null
}
}
type returnType = {
node: TreeNode,
total: number,
path: number[]
}
function findPath(tree: TreeNode): SingleNode | null {
if(!tree) return null;
// 构造记录路径
let record:returnType = {
node: tree,
total: tree.value,
path: [tree.value]
}
let maxValue = tree.value
function travelTree(node: TreeNode, record):returnType {
if(node.children?.length === 0
&& record.total > maxValue) {
maxValue = record.total
return record
}
for(let item of (node.children as TreeNode[])) {
record = travelTree(item, {
node: item,
total: record.total + item.value,
path: record.path.push(item.value)
})
}
return record
}
record = travelTree(tree, record)
let head
let point= head
const path = record.path
while(path.length) {
const val = <number>(path.shift());
if(!head) {
head = new SingleNode(val)
point = head
} else {
point.next = new SingleNode(val)
point = point.next
}
}
return head;
}
const grid = [
[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]
]
function maxAreaCount(grid:number[][]):number {
const rows = grid.length;
const cols = grid[0].length
let maxCount = 0
function dfs(a, i, j, count) {
if(i < 0 || j < 0 || i >= rows || j >= cols || a[i][j] === 0) {
return count
}
const arr = a.slice();
arr[i][j] = 0
// 上
if(a[i-1][j] === 1) count += dfs(arr,i - 1, j, count + 1);
// 右
if(a[i][j + 1] === 1) count += dfs(arr, i, j + 1, count + 1);
// 下
if(a[i+1][j] === 1) count += dfs(arr,i + 1, j, count + 1);
// 左
if(a[i][j - 1] === 1) count += dfs(arr, i, j - 1, count + 1);
}
for(let i = 0; i < rows; i++) {
for(let j = 0; j < cols; j++) {
if(grid[i][j] === 1) {
dfs(grid, i, j, 0)
}
}
}
return maxCount
}
maxAreaCount(grid)
/*
** 找出根节点到叶子节点的最大数字和的路径(使用广度优先算法)
** TreeNode {
** value: number;
** children: TreeNode[] | null
** }
** SingleNode {
** value: number;
** next: SingleNode | null
** }
*/
/**
*
* (1)
* (2,3,4)
* (5,6,8)(2,3,4)(1,2,3)
*/
class TreeNode {
value: number;
children: TreeNode[] | null;
constructor(value: number, children: TreeNode[] | null = null) {
this.value = value;
this.children = children;
}
}
class SingleNode {
value: number;
next: SingleNode | null;
constructor(value: number, next: SingleNode | null = null) {
this.value = value;
this.next = next;
}
}
interface valueType {
parent: TreeNode | null
value: number
}
function findPath(tree: TreeNode): SingleNode | null {
if(!tree) return null;
let queue = [tree];
// 构造map
const map = new Map<TreeNode, valueType>();
let maxValue = tree.value
let maxNode = tree
map.set(tree, { parent: null, value: tree.value })
// bfs
while(queue.length) {
const node = <TreeNode>(queue.shift());
const children = node.children || [];
const parentValue:number = map.get(node)?.value || 0
// 叶子节点
if(children.length === 0) {
if(parentValue > maxValue) {
// 记录最大叶子
maxValue = parentValue
maxNode = node
}
}
for(let item of children) {
map.set(item, { parent: node, value: item.value + parentValue })
queue.push(item);
}
}
let point:TreeNode|null = maxNode;
let path:number[] = []
let head:SingleNode | null = null
while(point) {
// 反向查找map
path.push(point.value)
const current = map.get(point) || {};
point = current.parent
}
for(let value of path) {
// 构造链表
head = new SingleNode(value, head)
}
return head
}
const child1 = new TreeNode(1)
const child2 = new TreeNode(2)
const child3 = new TreeNode(3)
const root = new TreeNode(1, [child1,child2,child3])
findPath(root)