新鲜面经出炉 By:2023.09.25

本文概述了面试中可能出现的基础技术点,包括数据结构(栈、堆和池)、JavaScript原型链、EventLoop的工作原理、ES6异步函数async/await、设计模式(如Redux、观察者模式等)以及前端后端登录模块的设计。还涉及了递归算法和数组操作问题解答。
摘要由CSDN通过智能技术生成

来自 上海某中小型新能源公司

反思:算法还是不够扎实,需要借助资料回忆,不能在面试中信手拈来

努力回忆......

第一问:池、堆、栈是用来存储什么的?

栈:存放基本类型的数据对象的引用,但对象本身不存放在栈中,而是存放在堆中。

堆: 堆中内存,存放的是对象实例,以及常量池。

池:有时候,我们不需要new出新对象,而是让栈中的地址直接指向常量池常量

第二问:JS原型链

原型链就是实例对象和原型对象之间的链接。每个函数都有一个prototype属性,这个prototype属性就是我们的原型对象,我们拿这个函数通过new构造函数创建出来的实例对象,这个实例对象自己会有一个指针(_proto_)指向他的构造函数的原型对象!这样构造函数和实例对象之间就通过( _proto_ )连接在一起形成了链条。

原型对象:JS的每个函数在创建的时候,都会生成一个属性prototype,这个属性指向一个对象,这个对象就是此函数的原型对象。该原型对象中有个属性为constructor,指向该函数。这样原型对象和它的函数之间就产生了联系。

第三问:event loop

Event Loop 即事件循环, 是JavaScript或Node为解决单线程代码执行不阻塞主进程一种机制,也就是我们所说的异步原理。事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。

JS为什么是单线程的?

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

回到问题 event loop

  • 整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为两部分:“同步任务”、“异步任务”;

  • 同步任务会直接进入主线程依次执行;

  • 异步任务会再分为宏任务(进入宏任务队列) 和 微任务(进入微任务队列)。

  • 当主线程内的任务执行完毕(主线程为空时),会检查微任务的任务队列,如果有任务,就进入主线程全部执行,如果没有就从宏任务队列读取下一个宏任务执行;

  • 每执行完一个宏任务就清空一次微任务队列,此过程会不断重复,这就是Event Loop;

第四问:ES6 async await函数

  1.  async 是异步的意思,await则可以理解为 async wait。所以可以理解async就是用来声明一个异步方法,而 await是用来等待异步方法执行
  2.  async作为一个关键字放在函数前面,表示该函数是一个异步函数,异步函数意味着该函数的执行不会阻塞后面代码的执行;而 await 用于等待一个异步方法执行完成;
  3.  当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 resolve 后再继续往下执行。并会阻塞该函数内后面的代码。
  4. 因此async/await的作用就是将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。
  5. 为了优化 .then 链而开发出来的。

第五问:在一个方法里面,有两个网络请求A和B,分别对应A方法和B方法,此时有await A(), 后面跟着await B(), 请问这是并行请求还是串行请求

串行请求

也就是说,B 方法中的网络请求会等待 A 方法中的网络请求完成后才会执行。

在 JavaScript 中,使用 await 关键字可以等待一个异步操作的完成,并且在该操作完成后再继续执行后续的代码。因此,当使用 await A() 后面跟着 await B() 的方式时,它们会按照顺序依次执行,而不是同时执行。

第六问,如果想把串行改成并行应该怎么做

将这两个网络请求并行执行,可以将它们放到 Promise.all() 方法中,并使用 await 等待 Promise.all() 的完成

async function myMethod() {
  await Promise.all([A(), B()]);
  // 这里可以处理 A 和 B 请求完成后的逻辑
}

第七问:Redux

第八问:设计模式相关,对应的场景

  1. 观察者模式(Observer Pattern):

    • 场景:当一个对象的状态发生变化时,需要通知其他多个对象,并且这些对象可能需要根据该状态变化做出响应。
    • 示例:在一个在线聊天应用中,当某个用户发送一条消息时,所有相关的用户会收到该消息的更新。
  2. 单例模式(Singleton Pattern):

    • 场景:当只需要实例化一个对象,并在任何地方都能访问该对象时。
    • 示例:在一个全局状态管理器(如 Redux)中,只需单个实例来管理整个应用的状态。
  3. 工厂模式(Factory Pattern):

    • 场景:当需要根据不同的条件来创建相似对象时。
    • 示例:在一个电商网站中,根据用户的购买历史,显示不同的推荐商品,需要使用工厂模式来创建不同类型的推荐商品对象。
  4. 适配器模式(Adapter Pattern):

    • 场景:当需要将一个对象的接口转换成另一个对象所期望的接口时。
    • 示例:在一个音频播放器应用中,需要将不同格式的音频文件(如 MP3、WAV)转换为统一的接口以供播放。
  5. 策略模式(Strategy Pattern):

    • 场景:当有多个算法或策略可以通用地应用于同一类问题时。
    • 示例:在一个表单验证的场景中,可以使用不同的验证策略(如长度、格式等)来根据不同的表单字段进行验证。
  6. 装饰器模式(Decorator Pattern):

    • 场景:当需要在不修改现有对象结构的情况下,动态地添加额外的行为或功能时。
    • 示例:在一个日志记录的场景中,可以使用装饰器模式来为现有的对象动态地添加日志记录功能。

第九问:工程方面的问题,实现一个登录的模块,从前端到后端怎么设计或者说流程

  1. 前端设计:

    • 创建登录表单:在前端创建一个登录表单,包含用户名和密码的输入字段,以及一个登录按钮。
    • 表单验证:在前端对用户名和密码进行基本的表单验证,例如检查是否为空、长度限制等。
    • 发起登录请求:当用户点击登录按钮时,在前端创建一个登录请求,将用户名和密码发送给后端。
  2. 后端设计:

    • 接收登录请求:后端需要创建一个接口来接收前端发送的登录请求。该接口应该使用 POST 方法,并接收用户名和密码作为请求参数。
    • 校验用户信息:后端接收到登录请求后,首先需要对接收到的用户名和密码进行校验。这可以包括验证用户名是否存在、密码是否正确等。
    • 创建会话:如果用户提供的用户名和密码是有效的,则后端可以创建一个会话来跟踪用户的登录状态。可以使用会话 ID、JWT(JSON Web Token)等方式进行会话管理,并将会话相关的信息存储在后端或数据库中。
    • 返回登录结果:根据登录请求的校验结果,后端需要将结果返回给前端。如果登录成功,通常会返回一个成功的响应,可能包含一些额外的用户信息。如果登录失败,可能会返回一个错误消息或状态码。
  3. 安全性考虑:

    • 加密密码:建议在前端对用户的密码进行加密,例如使用加盐哈希算法(如 bcrypt)来保护用户密码的安全性。
    • 防止暴力破解:前端和后端都应该实施一些策略来防止暴力破解,例如限制登录尝试次数。

第十问:手写算法题目

1)从1到n的和,用递归的方式

function sumRecursive(n) {
  // 递归的终止条件
  if (n === 1) {
    return 1;
  }
  
  // 调用递归函数来计算前 n-1 个数的和,并将结果与当前数 n 相加
  return n + sumRecursive(n - 1);
}

// 示例:计算从 1 到 5 的和
const result = sumRecursive(5);
console.log(result); // 输出 15

2)对于一个数组,找到符合要求的元素,该元素要比左边所有的数都小,比右边所有的数都大

function findElement(arr) {
  for (let i = 1; i < arr.length - 1; i++) {
    let currentElement = arr[i];
    let isLeftSmaller = true;
    let isRightBigger = true;
    
    // 检查当前元素是否比左边所有的数都小
    for (let j = 0; j < i; j++) {
      if (arr[j] >= currentElement) {
        isLeftSmaller = false;
        break;
      }
    }
    
    // 检查当前元素是否比右边所有的数都大
    for (let k = i + 1; k < arr.length; k++) {
      if (arr[k] <= currentElement) {
        isRightBigger = false;
        break;
      }
    }
    
    // 如果当前元素符合要求,则返回
    if (isLeftSmaller && isRightBigger) {
      return currentElement;
    }
  }
  
  // 如果没有找到符合要求的元素,则返回 null 或其他值
  return null;
}

// 示例:查找符合要求的元素
const array = [2, 5, 3, 7, 1, 8, 9];
const result = findElement(array);
console.log(result); // 输出 7

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值