面经2
根据网上面经,问题回答
一面
项目:
1.为什么用websocket,知道怎么建立连接的吗
WebSocket 的优势:
实时通信
WebSocket 允许在客户端和服务器之间建立持久连接,实现双向通信,适合实时应用(如聊天应用、在线游戏等)。减少延迟
与传统的 HTTP 请求相比,WebSocket 减少了请求和响应的延迟。节省带宽
WebSocket 连接一旦建立,后续的数据传输不需要重复 HTTP 头部,从而节省带宽。建立 WebSocket 连接的示例:
const socket = new WebSocket('ws://yourserver.com/socket');
socket.onopen = function(event) {
console.log('WebSocket is open now.');
};
socket.onmessage = function(event) {
console.log('Message from server: ', event.data);
};
socket.onclose = function(event) {
console.log('WebSocket is closed now.');
};
socket.onerror = function(error) {
console.error('WebSocket error: ', error);
};
2.说一下小程序的双线程模型
小程序采用了主线程和Web Worker(子线程)的双线程模型:
主线程
负责 UI 渲染和事件处理。子线程
用于处理耗时的任务(如网络请求、数据处理),避免阻塞主线程,提高用户体验。3.为什么用echart,echart在小程序中是怎么渲染的
使用 ECharts 的原因:
丰富的图表类型:ECharts 提供多种图表类型,可以满足不同的数据可视化需求。 高度可定制:支持自定义样式和交互,适合多样化的业务需求。 良好的性能:优化的渲染性能,适合大数据量的展示。 在小程序中的渲染:
使用 ECharts 的小程序版(如 ec-canvas 组件),通过 Canvas 渲染图表。
// 在小程序中使用 ECharts
import * as echarts from 'echarts';
const chart = echarts.init(document.getElementById('myChart'));
const option = {
title: { text: 'ECharts 示例' },
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
};
chart.setOption(option);
前端:
4.promise
Promise 是一种用于处理异步操作的对象,代表一个可能在未来某个时间点完成或失败的操作。 基本用法:
const myPromise = new Promise((resolve, reject) => {
// 异步操作
if (/* success */) {
resolve('成功');
} else {
reject('失败');
}
});
myPromise
.then(result => console.log(result))
.catch(error => console.error(error));
5.var let const
var:
- 作用域:函数作用域。
- 变量提升:会被提升到函数或全局顶部。
let:
- 作用域:块级作用域。
- 变量提升:不会被提升到块外。
const:
- 作用域:块级作用域。
- 变量提升:不会被提升到块外。
- 赋值后不可更改(但对象的属性可以修改)。
6.小程序的生命周期
小程序的生命周期包括以下几个阶段:
onLaunch
小程序初始化时触发。onShow
小程序启动或从后台进入前台时触发。onHide
小程序从前台进入后台时触发。onError
小程序发生错误时触发。算法:
7.promise执行顺序,说一下就行
Promise 是异步操作,执行顺序遵循微任务队列。 then 方法中的回调会在当前执行栈清空后立即执行。
console.log('Start');
const promise = new Promise((resolve) => {
console.log('Promise');
resolve();
});
promise.then(() => {
console.log('Promise resolved');
});
console.log('End');
//输出顺序:
Start
Promise
End
Promise resolved
8.二叉树打印路径,每条路径上的数字相加
使用深度优先搜索(DFS),遍历二叉树。 记录当前路径上的节点值,当到达叶子节点时,计算路径和并输出。 代码示例:
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
function printPaths(root, path = [], sum = 0) {
if (!root) return;
path.push(root.value);
sum += root.value;
// 如果是叶子节点
if (!root.left && !root.right) {
console.log(`Path: ${path.join('->')}, Sum: ${sum}`);
} else {
// 递归遍历左右子树
printPaths(root.left, path.slice(), sum);
printPaths(root.right, path.slice(), sum);
}
}
// 示例用法
const root = new TreeNode(5);
root.left = new TreeNode(4);
root.right = new TreeNode(8);
root.left.left = new TreeNode(11);
root.left.left.left = new TreeNode(7);
root.left.left.right = new TreeNode(2);
root.right.left = new TreeNode(13);
root.right.right = new TreeNode(4);
root.right.right.right = new TreeNode(1);
printPaths(root);
二面
项目:
1.人员组成,技术选型,重点在为什么
人员组成
项目团队通常包括前端开发、后端开发、UI/UX 设计师、测试工程师等。技术选型
- 前端:选择 React 或 Vue 作为框架,因其组件化和生态系统丰富。
- 后端:选择 Node.js 或 Java,因其高性能和广泛的社区支持。
- 数据库:选择 MongoDB 或 MySQL,根据项目需求(文档型或关系型)。
重点原因
技术选型基于团队的技术栈熟悉度、项目需求、性能考虑和未来可维护性。框架:
2.什么是虚拟dom,作用是什么(面试官提了一个跨端)
虚拟 DOM 是一种轻量级的 JavaScript 对象,表示真实 DOM 的结构。它的作用包括:
性能优化:通过计算差异,减少直接对真实 DOM 的操作,提高渲染性能。 跨平台:可以在不同平台上使用相同的代码(如 React Native)。
3.知不知道react fiber(我还真不知道)
React Fiber 是 React 16 引入的重新实现的核心算法,旨在提高性能和可扩展性。
增量渲染:允许将渲染过程分解为多个小任务,避免阻塞主线程。 优先级调度:支持根据任务的优先级进行调度,提高用户体验。
4.Memo是什么
Memo 是 React 中的一个高阶组件,用于优化组件性能。它通过对比前后 props 和 state,决定是否重新渲染组件。
const MemoizedComponent = React.memo(MyComponent);
5.react状态管理(我说比较熟悉vue,问了vuex和pinia的区别)
Vuex3/4:对应Vue2/3 的状态管理库,基于单向数据流,适合大型 Vue 应用。 Pinia:Vue 3 的状态管理库,更加轻量,支持 Composition API,使用更简单。
前端:
6.闭包,怎么防止闭包内存泄漏
闭包 是一个函数与其词法环境的组合,可以访问外部函数的变量。防止闭包内存泄漏的方法:
及时清理:在不需要时,手动清除对外部变量的引用。 使用 WeakMap:在一些情况下,可以使用 WeakMap 来存储闭包,避免内存泄漏。
7.知不知道TypeScript,主要区别是什么
TypeScript 是 JavaScript 的超集,添加了静态类型和其他特性。主要区别:
类型系统:TypeScript 提供静态类型检查,而 JavaScript 是动态类型。 编译阶段:TypeScript 在编译时捕捉错误,JavaScript 在运行时。
计算机基础:
8.栈和队列的区别
栈:后进先出(LIFO),使用 push 和 pop 操作。 队列:先进先出(FIFO),使用 enqueue 和 dequeue 操作。
9.数组和链表的区别,如果对数组扩容,内存怎么分配
- 数据结构
- 数组:
是一种线性数据结构,包含一系列相同类型的元素,内存是连续分配的。 支持随机访问,可以通过索引快速访问元素。
- 链表:
是一种非线性数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。 不支持随机访问,访问元素需要从头节点开始逐个遍历。
- 内存分配
- 数组:
内存是静态分配(固定大小)或动态分配(可变大小)。 当数组大小固定时,内存分配在编译时确定;当动态扩容时,内存在运行时分配。
- 链表:
内存是动态分配的,每个节点单独分配内存。 节点的数量可以在运行时动态变化,不需要预先定义大小。
- 时间复杂度
- 数组:
访问:O(1)(通过索引直接访问) 插入/删除:O(n)(需要移动元素)
- 链表:
访问:O(n)(需要遍历) 插入/删除:O(1)(在已知位置插入或删除)
- 使用场景
- 数组:适用于需要频繁访问元素的场景,如查找和排序。
- 链表:适用于需要频繁插入和删除元素的场景,如实现队列和栈。 数组扩容时的内存分配 当数组需要扩容时,通常会遵循以下步骤:
创建新数组
创建一个新的数组,通常是原数组大小的 1.5 倍或 2 倍,以减少未来的扩容次数。 复制元素:
将原数组中的元素复制到新数组中。这个过程是 O(n) 的时间复杂度,因为需要遍历原数组。 释放原数组:
原数组的内存会被释放,新的数组将替代原数组。 示例:
let originalArray = [1, 2, 3];
let newSize = originalArray.length * 2; // 新数组大小
let newArray = new Array(newSize); // 创建新数组
// 复制元素
for (let i = 0; i < originalArray.length; i++) {
newArray[i] = originalArray[i];
}
// 释放原数组(在 JavaScript 中,垃圾回收会自动处理)
originalArray = newArray; // 更新引用
算法:
10.看 var 和 let 定义的变量(变量提升)
变量提升(Hoisting) 在 JavaScript 中,变量提升是指变量的声明(但不包括初始化)被提升到其所在作用域的顶部。这适用于使用 var、let 和 const 声明的变量,但它们的行为略有不同。
- 使用 var 声明的变量 var 声明的变量会被提升到函数或全局作用域的顶部。 在声明之前访问该变量会得到 undefined,而不是抛出错误。 示例:
复制 console.log(a); // 输出: undefined var a = 5; console.log(a); // 输出: 5 解释:
变量 a 的声明被提升到顶部,但初始化(赋值)仍然在原位置执行。因此,第一次 console.log(a) 输出 undefined。 2. 使用 let 和 const 声明的变量 let 和 const 声明的变量同样会被提升,但它们不会被初始化。 在声明之前访问这些变量会导致 ReferenceError,因为它们处于“暂时性死区”(Temporal Dead Zone,TDZ)内。 示例:
复制 console.log(b); // 报错: ReferenceError: Cannot access 'b' before initialization let b = 10; console.log(b); // 输出: 10 解释:
变量 b 的声明被提升,但在其实际声明之前访问会导致错误,因为它处于暂时性死区。 总结 var:
声明提升到顶部,初始化在声明位置执行。 在声明之前访问会返回 undefined。 let 和 const:
声明也被提升,但不初始化。 在声明之前访问会导致 ReferenceError,因为它们处于暂时性死区。
11.合并两个有序链表
合并两个有序链表 思路:
使用双指针遍历两个链表,比较节点值,依次添加到新链表中。
class ListNode {
constructor(value = 0, next = null) {
this.value = value;
this.next = next;
}
}
function mergeTwoLists(l1, l2) {
const dummy = new ListNode();
let current = dummy;
while (l1 && l2) {
if (l1.value < l2.value) {
current.next = l1;
l1 = l1.next;
} else {
current.next = l2;
l2 = l2.next;
}
current = current.next;
}
// 连接剩余部分
current.next = l1 || l2;
return dummy.next; // 返回合并后的链表
}
// 示例用法
const l1 = new ListNode(1, new ListNode(2, new ListNode(4)));
const l2 = new ListNode(1, new ListNode(3, new ListNode(4)));
const mergedList = mergeTwoLists(l1, l2);