Skip to content

面经2

根据网上面经,问题回答

一面

项目:

1.为什么用websocket,知道怎么建立连接的吗

WebSocket 的优势:

实时通信
WebSocket 允许在客户端和服务器之间建立持久连接,实现双向通信,适合实时应用(如聊天应用、在线游戏等)。
减少延迟
与传统的 HTTP 请求相比,WebSocket 减少了请求和响应的延迟。
节省带宽
WebSocket 连接一旦建立,后续的数据传输不需要重复 HTTP 头部,从而节省带宽。

建立 WebSocket 连接的示例:

js
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 渲染图表。

js
// 在小程序中使用 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 是一种用于处理异步操作的对象,代表一个可能在未来某个时间点完成或失败的操作。 基本用法:

js
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 方法中的回调会在当前执行栈清空后立即执行。

js
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),遍历二叉树。 记录当前路径上的节点值,当到达叶子节点时,计算路径和并输出。 代码示例:

js
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,决定是否重新渲染组件。

js
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.数组和链表的区别,如果对数组扩容,内存怎么分配

  1. 数据结构
  • 数组:

是一种线性数据结构,包含一系列相同类型的元素,内存是连续分配的。 支持随机访问,可以通过索引快速访问元素。

  • 链表:

是一种非线性数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。 不支持随机访问,访问元素需要从头节点开始逐个遍历。

  1. 内存分配
  • 数组:

内存是静态分配(固定大小)或动态分配(可变大小)。 当数组大小固定时,内存分配在编译时确定;当动态扩容时,内存在运行时分配。

  • 链表:

内存是动态分配的,每个节点单独分配内存。 节点的数量可以在运行时动态变化,不需要预先定义大小。

  1. 时间复杂度
  • 数组:

访问:O(1)(通过索引直接访问) 插入/删除:O(n)(需要移动元素)

  • 链表:

访问:O(n)(需要遍历) 插入/删除:O(1)(在已知位置插入或删除)

  1. 使用场景
  • 数组:适用于需要频繁访问元素的场景,如查找和排序。
  • 链表:适用于需要频繁插入和删除元素的场景,如实现队列和栈。 数组扩容时的内存分配 当数组需要扩容时,通常会遵循以下步骤:

创建新数组

创建一个新的数组,通常是原数组大小的 1.5 倍或 2 倍,以减少未来的扩容次数。 复制元素:

将原数组中的元素复制到新数组中。这个过程是 O(n) 的时间复杂度,因为需要遍历原数组。 释放原数组:

原数组的内存会被释放,新的数组将替代原数组。 示例:

js
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 声明的变量,但它们的行为略有不同。

  1. 使用 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.合并两个有序链表

合并两个有序链表 思路:

使用双指针遍历两个链表,比较节点值,依次添加到新链表中。

js
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);