Web前端龙年一面问题记录

56 阅读12分钟

笔试题

写出执行结果

for (let i = (setTimeout(() => { console.log(i) }, 50), 0); i < 5; i += 1) {
  console.log(i)
}

// 执行结果: 0, 1, 2, 3, 4, 0 👈🏻这个是setTimeout中的log
// setTimeout中的log执行的时候先被压入消息队列, 待调用栈执行结束后从消息队列取出后再执行

// 考察解析: 闭包
// let i = (xx,x,xxx); 逗号操作符用于分隔多个表达式,并且返回最后一个表达式的值
//   主要用于重写运算符的优先级,会按照从左到右的顺序执行,并将最后一个值赋值给等式左边的变量
//   举例: const a = (1,2,3) // a = 3
写出执行结果

setTimeout(() => {
    console.log(1)
})

const p = new Promise(resolve => {
    resolve()
    console.log(2)
})

p.then(() => {
    console.log(3)
})

console.log(4)

// 执行结果: 2,4,3,1
// 考察解析: EventLoop
// 解析: 👇

// 4. 压入消息队列, 待执行栈为空且微任务队列也为空的时候被压入调用栈所出执行
setTimeout(() => {
  console.log(1) 
}) //此处省略了第二个参数, 默认为 0

// 1. 压入调用栈立即执行, 遇resolve被调用,意味着Promise对象已经成功完成. 继续执行console.log(2)
const p = new Promise(resolve => {
  resolve()
  console.log(2)
})

// 3. 遇到p的then回调函数, 被压入微任务队列, 待调用栈为空的时候被压入调用栈所执行
p.then(() => {
  console.log(3)
})

// 2. 压入调用栈立即执行
console.log(4)
实现一种数组乱序算法, 例如 [1,2,3,4,5] => [6,3,1,2,5,4]
// 写的是最简单和常见的一种
const randomSort = (arr: number[]) => arr.sort(() => 0.5 - Math.random())

面试

vue2和vue3的区别

  • 速度更快
    • 重写虚拟DOM实现
    • 编译模板优化
    • 更高效的组件初始化
    • undate性能提高
    • SSR速度提高
  • 体积更小
    • 结合webpacktree-shaking功能仅打包需要的代码
      • tree-shaking:
        • 对开发者,不必担心整体体积过大
        • 对使用者,打包的体积变小
  • 更易维护
    • 新增composition API
      • 灵活的逻辑组合与复用
      • 可与现有的Options API一起使用
      • vue3模块可以搭配其他框架使用
  • 更好的Typescript支持
    • vue3基于typescript编写
  • 编译器重写
  • 更接近原生
    • 自定义渲染API
      • import { createRender } from '@vue/runtime-core'
  • 更易使用
    • 响应式API的暴露
      • import { observable } from 'vue'
  • 新特性
    • Framents
      • vue3版本中, 页面或者组件支持多个根节点
        <template>
            <header />
            <main />
            <footer />
        </template>
        
    • Teleport
      • 利用Teleprot能够将DOM移动到其他位置的技术
        <button @click="showToast" class="btn">打开 toast</button>
        <!-- to 属性就是目标位置 -->
        <teleport to="#teleport-target">
            <div v-if="visible" class="toast-wrap">
                <div class="toast-msg">我是一个 Toast 文案</div>
            </div>
        </teleport>
        
    • CreateRender
      • 通过CreateRender构建自定义渲染器
        import { createRenderer } from '@vue/runtime-core'
        
        const { render, createApp } = createRenderer({
          patchProp,
          insert,
          remove,
          createElement,
          // ...
        })
        
        export { render, createApp }
        
    • CompositionAPI
    • ...

GET和POST的区别

  • 协议解释
    • GETPOST都是HTTP协议的请求方法, 二者都是TCP链接
    • GET方法用于请求一个地址的资源, 使用GET的请求应该只用于数据的获取
    • POST方法用于将数据提交到指定的资源地址, 通常会使服务器上产生一些变化或者副作用
  • 区别
    • GET请求在浏览器后退的时候无害, POST会再次提交表单
    • GET产生的URL地址可以被添加为Bookmark(书签), POST不可以
    • GET请求会被浏览器主动cache, POST在非手动设置下不会
    • GET请求只能进行URL编码, POST支持多种编码方式
    • GET请求的参数会被完整保存在浏览器历史记录中, POST的参数不会被保留
    • GET请求在URL中传输的参数长度有限, POST没有
    • GETPOST更加不安全, 因为参数直接暴露在URL中, 不适宜传递敏感信息
      • POST请求可能会自动变成OPTIONS请求, 可能原因如下
        • 遇 跨域资源共享(CORS),服务端未配置CORS情况下Ajax请求被拒绝,变成了OPTIONS请求进行预检请求
        • 预检请求: 在发送实际请求前, 发送OPTIONS请求获取服务器支持的HTTP请求方法或者检查服务器性能.
          • 如果服务器返回的响应头中包含了ALLOW, 那么就标识了服务器所支持的HTTP请求方法
    • GET参数通过URL传递, POST放在Request Body
    • 参数数据类型不一致,GET请求只接受ASCII字符, POST没有限制

vue和react的区别

  • 核心思想不同
  • diff算法不同
  • 响应式原理不同
    • vue使用Object.definePropertyProxy
    • react使用setState
    • ...
  • 学习曲线不同
  • ...

const和readonly的区别

  • const用于声明一个常量对象, const必须在声明时候立即初始化, 该对象一旦赋值后就不能再被赋值, 如果对象是一个具有属性的对象, 其属性是可以修改的.
  • readonly用于声明或者修饰一个对象设置为只读, 包含其属性也不能进行修改, 可以理解为 Object.freeze()

什么是跨域、如何解决跨域

  • 跨域(Cross-Origin-Resource-Sharing, CORS) 是浏览器出于安全策略限制了,一个域(协议、域名、端口)下的网页无法直接访问另一个域下的资源.
  • 解决跨域
    • JSONP(仅限GET请求):JSONP利用<script>标签可以跨域加载资源的特性,通过在服务器端返回一个包含回调函数的JSON字符串,客户端通过这个回调函数处理数据
    • CORS(Cross-Origin Resource Sharing):服务器需要设置Access-Control-Allow-OriginAccess-Control-Allow-Methods等响应头,允许特定的源进行跨域请求
    • 代理服务器:在服务器端设置一个代理,将请求转发到目标服务器,然后返回结果。这样,浏览器认为请求和响应都是来自同一源,从而绕过同源策略。
    • PostMessage:HTML5引入的window.postMessage方法允许不同源的页面之间安全地传递消息。通过这个API,页面可以发送消息给另一个窗口或iframe,只要它们在同一文档下
    • Document.domain:当两个页面属于同一个主域(主域相同,子域不同)时,可以通过设置document.domain为相同的主域来实现跨域通信
    • WebSocketWebSocket协议允许在浏览器和服务器之间建立全双工通信,它不受同源策略的限制,可以用于实现跨域通信
    • 使用第三方服务:如CORS Anywhere、Apigee等,这些服务可以作为中间代理,帮助处理跨域请求
    • Nginx反向代理:在Nginx服务器配置中设置反向代理,可以将请求转发到目标服务器,并在响应头中添加Access-Control-Allow-Origin等CORS相关的头信息

什么是cookie?如何设置cookie (客户端)

  • cookie是一种存储在用户浏览器本地的小型文本文件。当用户访问网站时候,服务端可以创建一个cookie将其发送到用户的浏览器。浏览器随后会将这个cookie保存到本地,并在后续访每次向服务器发送请求的时候自动携带这个cookie
    • cookie的大小通常有限制(例如单个cookie大小通常不超过4KB),而且每次HTTP请求都会携带cookie从而影响性能。
    • 由于cookie是存储在客户端,所以它可能存在安全风险,因为用户可以查看和修改cookie内容。
  • 示例
    • nest.js
    // 使用 cookie-parser 插件
    npm i cookie-parser
    npm i -D @types/cookie-parser
    
    // 注册插件 省略其他引入
    import * as cookieParser from 'cookie-parser';
    
    async function bootstrap () {
      const app = await NestFactory.create(AppModule, {
        cors: true,
      });
      app.use(cookieParser());
    }
    bootstrap()
    
    // 使用
    import { Controller, Get, Req, Res } from '@nestjs/common';
    import { Response, Request } from 'express';
    
    @Controller()
    export class AppController {
    
      @Get('set/cookie')
      setCookie(@Res() res: Response) {
        res.cookie('cookieName', 'cookieValue', {
          maxAge: 1000 * 60 * 10, // 10分钟
          httpOnly: true, // 只有服务器可以访问Cookie
          signed: true, // 签名Cookie,提高安全性
        })
        res.send('cookie set success')
      }
    
      @Get('get/cookie')
      getCookie (@Req() req: Request) {
        const cookie = req.cookies['cookieName']
      }
    }
    

什么是 session(服务端)

  • session是一种服务端机制,用于存储和管理用户的会话信息。每个用户都拥有一个唯一的session ID
  • sessioncookie存储更多数据且没有明确大小限制。常用于存储用户的身份验证信息、购物车、用户设置等
  • sessioncookie更安全,因为session数据存储在服务端,用户无法直接修改或者访问
  • 示例
    • nest.js
    // 安装依赖
    npm install express-session @types/express-session --save
    
    // 配置Session中间件
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import * as session from 'express-session';
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
    
      // 配置Session中间件
      app.use(
        session({
          secret: 'your-secret-key', // 用于加密Session ID
          resave: false,
          saveUninitialized: true,
          cookie: { secure: true }, // 如果你使用HTTPS
        }),
      );
    
      await app.listen(3000);
    }
    bootstrap();
    
    // 使用
    import { Controller, Get, Post, UseGuards, Request, Session } from '@nestjs/common';
    import { AuthGuard } from '@nestjs/passport';
    import { Session } from 'express-session';
    
    @Controller()
    export class AppController {
      constructor() {}
    
      @Get('session')
      async getSession(@Session() session: Session) {
        console.log(session); // 获取Session数据
        return 'Session data';
      }
    
      @Post('login')
      @UseGuards(AuthGuard('local'))
      async login(@Request() req) {
        // 在用户登录后,你可以将用户信息存储到Session中
        req.session.user = {
          id: 1,
          username: 'johndoe',
        };
        return 'User logged in';
      }
    }
    

浏览器事件循环和node.js的事件循环的区别

  1. 执行环境的区别
    • 浏览器事件循环运行在浏览器中的JavaScript引擎中, 主要处理DOM操作, 用户交互, 网络请求等
    • node.js运行在node.js环境中, 处理文件系统操作, 网络通信, 进程控制等
  2. 事件循环的阶段
    • 浏览器事件循环通常包括宏任务(如script执行、setTimeOut、setInterval、requestAnimationFrame、UI事件处理等), 微任务(如Promise.then、MutationObserver), 调用栈执行
    • node.js事件循环由libuv库实现, 包括多个阶段, 如 timer、I/O、callbacks、idle、prepare、poll、check、close callbacks等
  3. 宏任务与微任务的处理:
    • 浏览器在每个宏任务执行完毕后,会立即执行当前微任务队列中的所有微任务
      • _(:з」∠)_ 这里不是指宏任务的队列的优先级高于微任务队列优先执行
    • 在某些版本中,微任务的处理可能与浏览器有所不同。例如,在Node.js v11及以上版本中,微任务的执行顺序与浏览器相似;而在Node.js v10及以下版本中,微任务的执行可能受到宏任务执行的影响。
  4. I/O操作
    • 浏览器的事件循环通常不会直接处理I/O操作, 而是通过委托给浏览器底层系统
    • node.js的事件循环直接处理I/O操作, 如文件读写, 网络请求等
  5. 性能和优化
    • 浏览器事件循环需要考虑渲染性能, 可能会在某些情况下限制事件循环执行, 以避免阻塞UI渲染
    • node.js事件循环更注重I/O密集型任务性能, 通常I/O操作等待期间会进行其他任务处理
  6. 平台差异
    • 浏览器的事件循环需要在不同的浏览器和操作系统上保持一致
    • node.js的事件循环依赖底层的libuv库, 使得node.js在不同操作系统上实现类似异步I/O处理

在浏览器地址输入URL按下回车之后发生了什么

  1. URL解析
    • 浏览器首先会检查输入的URL是否有效, 如果URL格式正确, 浏览器会解析URL, 提取协议(http/https)、域名、端口(如果有)和路径
  2. 检查缓存
    • 浏览器会检查本地缓存(http缓存、dns缓存)看是否有当前URL的缓存数据。如果有缓存同时缓存的数据比较新,浏览器会可能会直接使用缓存,跳过后续步骤
  3. DNS解析
    • 如果浏览器没有缓存或者缓存的数据比较老, 浏览器会进行DNS(Domain Name System)解析,将域名转换成服务器的IP地址。这个过程可能涉及多个DNS服务器,包括本地DNS缓存、根DNS域名服务器等
  4. 建立TCP连接
    • 一旦有了服务器的IP地址, 浏览器会尝试与服务器通过TCP建立连接。这个过程通常涉及三次握手(SYN、SYN-ACK、ACK)
  5. 发送HTTP请求
    • TCP连接建立后, 浏览器会构造一个HTTP请求(GET请求), 包括请求头和请求体(如果有), 并发送给服务器
  6. 服务器处理请求
    • 服务器接收到请求后, 会根据请求的URL和参数处理请求。这可能会包括数据库的查询、执行服务器端的脚本等等
  7. 服务器响应
    • 服务器处理完成后, 会返回一个HTTP响应给浏览器, 包括状态码、响应头和响应体(包含网页内容)
  8. 处理响应
    • 浏览器接收到服务器的响应后, 会根据响应头中的信息(如Content-Type)处理响应体。如果是HTML文档, 浏览器则会开始解析HTML, 构建DOM树
  9. 渲染页面
    • 在解析HTML同时, 浏览器会下载并解析CSSJavaScript文件。JavaScript可能会修改DOM树, 执行动画等等。CSS用于计算样式, 构建CSSOM树。然后浏览器会将DOM树CSSOM树合成并生成渲染树, 进行布局(回流)和绘制(重绘), 最终显示在屏幕上
      • DOM树构建完成会触发 - DOMContentLoadeds事件
  10. 资源加载
    • 页面中的图片、视频资源也会被加载。如果资源需要, 浏览器可能会并行加载这些资源, 以提高加载速度.
      • Chrome资源并行数量限制为6个, why?
        • 操作系统端口资源的考虑: 在TCP/IP协议中, 每个TCP都需要占用一个端口
        • 线程切换开销: 过多的并发请求会导致频繁的线程切换, 会产生额外的开销
        • 客户端良知机制: 防止客户端无限制地发起请求, 从而导致服务端资源被过度占用
        • 避免服务端并发阈值: 服务端通常也会设置并发请求的阈值, 以防止恶意攻击或者过度请求
        • HTTP/1.1的Keep-Alive特性: HTTP/1.1支持Keep-Alive, 允许一个TCP连接上发送多个HTTP请求, 这样可以减少建立连接和关闭连接的开销, 同时限制并发数量从而更有效的利用该特性来提高资源加载效率
  11. 页面交互
    • 用户开始与页面交互, 点击链接, 表单提交等等, 这些操作都会触发新的HTTP请求
  12. 断开连接
    • 当页面被使用完成, 用户关闭页面时候, 浏览器会关闭也服务器的TCP连接

尾巴

这仅仅作为个人面试记录与复盘, 对看见的人如果有所帮助, 那自然是更好的了, 如果有错误欢迎指正.

本篇内容均靠个人知识见解和互联网内容检索编写而成.

_(:з」∠)_