牛客面经每日一总结(五)

193 阅读11分钟

HTTP 缓存过期的原理?

不知道我这样的理解对不对。

  • last-modified 和 if-modified-since不一样。
  • etag 和 if-none-match 不一样。
  • expires规定的缓存时间到了
  • cache-control设置的max-age到了。

DNS 缓存设置?

他会将查找的到的ip和域名的映射缓存在浏览器和本地DNS服务器中。

设计模式:发布订阅、观察者、单例模式、工厂模式

发布订阅者模式

他就是我们经常使用的第三方库,比如mitt,events等。

他的原理

  • 定义一个map,或者一个对象。
  • 将相同的自定义的事件名放在同一个数组或者set中。
  • 然后触发当前指定的事件时,依次循环调用数组或者set中的事件。

观察者模式

vue的响应式就是通过这种设计模式设计的。

其实这种设计模式和发布订阅者模式非常像。他两的区别是观察者模式中观察者和被观察者存在依赖关系,就是被观察者改变,那么观察者也会触发对应的内容。但是发布订阅者模式发布和订阅并没有相应的联系。

vue响应式原理实现,请查看这里

单例模式

这个是一个很经典的设计模式,他只能让外界创建唯一一个实例。其实js中并没有说真正的单例模式,因为js中的类并不具有封装性。没有像private这种权限修饰符。

    class Singleton {
      private constructor(private name: string) {}

      private static instance: Singleton

      static getInstance(name: string) {
        if (this.instance !== null) {
          return this.instance
        } else {
          this.instance = new Singleton(name)
        }
      }
    }

    const a: Singleton = Singleton.getInstance('zh')
    const b = Singleton.getInstance('zh')
    console.log(a === b) // true

工厂模式

通过传入不同的参数,替用一个创建对象的接口。

具体请看这里

vue出现空白页面的原因

  • 路由路径匹配错误
  • 未配置该路径下的路由组件映射
  • 配置两个相同的路径,并进行的重定向。 其实我在开发中还没有说遇见过显示空白页面。

vue的兼容性

  • vue2的兼容性 Vue 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性。但它支持所有兼容 ECMAScript 5 的浏览器。
  • vue3的兼容性 他不支持IE11及以下版本,因为vue3的响应式原理是通过proxy实现的。proxy的兼容性如下。 image.png
  • Chrome Latest
  • Firefox Latest
  • Opera Latest
  • Edge 13 +
  • IE 11+
  • Safari 9+
  • IOS 9+
  • Android 4.4+
  • Windows Mobile IE 11+
  • Vue 3 不支持 IE11及以下浏览器

==的隐式类型转换

== 两边的操作数,最后都会通过ToNumber来将其转换为number类型进行比较。

如果操作数是对象类型,那么将通过ToPromitive来将其转为基本数据类型,然后再通过ToNumber来转为数字进行比较。

  [] == false // true
  [1] == false // false

优化性能

  • CDN
  • 图片懒加载,预加载 @vue/preload-webpack-plugin
  • 资源缓存
  • 小图片转为base64格式,减少网络请求。
  • 路由懒加载
  • UI组件库的按需引入
  • webpack进行分包(多入口, splitChunks, import),减少首屏氢气的js文件大小。
  • 图片压缩(image-minimizer-webpack-plugin)
  • 骨架屏,loading
  • 开启资源请求压缩。accept-encoding: gzip, content-ecoding: gzip。
  • 避免在主线程中执行大量的js计算。通过web works。注意这里不能操作dom。

promise实现红路灯问题

题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?

  • setTimeout定义执行时间
  • 递归让其不断交替。
    function red() {
      console.log('red')
    }
    function green() {
      console.log('green')
    }
    function yellow() {
      console.log('yellow')
    }

    const run = () =>
      Promise.resolve()
        .then(() => wrap(red, 3))
        .then(() => wrap(green, 2))
        .then(() => wrap(yellow, 1))
        .then(() => run())

    function wrap(fn, time) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          fn()
          resolve()
        }, time * 1000)
      })
    }

    run()

实现lodash中的get方法

一让我写这种算法,头都大了,虽然可以写出来,但是可能需要很长时间。不断试错。

xdm,我tm菜死了。啊啊啊啊啊啊。

    function get(obj, str) {
      let reg = /\[([0-9])\]/g
      const arr = str.split('.')

      // 保存当前属性取值对象
      let current = null
      // console.log('=====', arr, reg.exec('b[0]'))
      for (let i = 0; i < arr.length; i++) {
        if (reg.test(arr[i])) {
          // 正则匹配数组取值。
          // [ 'a', [ 'b', '[0]' ], 'c', 'a' ]
          arr[i] = [arr[i].split('[')[0], arr[i].split('[')[1].slice(0, 1)]
        } else {
          continue
        }
      }

      for (let i = 0; i < arr.length; i++) {
        // 判断该item是否是数组取值。
        if (Array.isArray(arr[i])) {
          // 当该item时一个数组时 [b, [0]]
          if (isHasKey(current || obj, arr[i][0])) {
            // console.log(o.a.b[0].c.a)
            if (current) {
              // 当前值不是已经被赋值
              current = current[arr[i][0]][arr[i][1]] // o.a["b"]
            } else {
              current = arr[i][0][arr[i][1]]
            }
          } else {
            // 传入的对象中不能找到属性
            return undefined
          }
        } else {
          // item不是一个数组
          // o.a.b[0].c.a
          if (isHasKey(current || obj, arr[i])) {
            // 判断该属性是否在这里面
            if (current) {
              current = current[arr[i]] // o["a"][]
            } else {
              current = obj[arr[i]] // o["a"]
            }
          } else {
            // 传入的对象不能找到属性
            return undefined
          }
        }
      }
      return current
    }

    function isHasKey(obj, str) {
      return Object.keys(obj).includes(str)
    }

测试

    console.log(
      get(
        {
          a: {
            b: [
              {
                c: {
                  a: 'zh'
                }
              }
            ]
          }
        },
        'a.b[0].c.a'
      )
    )

我的个人网站是如何实现登录的

前端就发送ajax请求,携带request body。

后端,通过jwt机制生成token来验证用户的登录状态。

  async useJwt(username) {
    const { ctx, app } = this

    // 使用jwt
    const token = app.jwt.sign(
      // payload: 包括一些数据,和默认的iat:签名的时间
      {
        username
      },
      // signature, 设置秘钥
      app.config.jwt.secret
    )
    // 设置session,判断用户是否登录。保存用户登录状态
    ctx.session[username] = token
    return token
  }

  // 在需要登录的时候,在header中携带token。 header.token
  async login() {
    const { ctx } = this
    // const username = ctx.request.body.username;
    const res = await ctx.service.admin.login(ctx.request.body)
    if (res) {
      // 使用jwt
      const token = await this.useJwt(res.userName)
      this.success({
        token,
        isSuccess: true
      })
    } else {
      this.success({
        isSuccess: false
      })
    }
  }

验证用户登录状态,比较前端传递的token和后端保存的session。


'use strict';

module.exports = options => {
  return async function adminauth(ctx, next) {
    const url = ctx.request.url.split('?')[0];

    const sessionValue = ctx.session[ctx.username];
    // 获取请求传递的token
    const token = ctx.request.header.token;
    // 判断前端和后端的token是否相等
    const user = sessionValue === token;
    if (ctx.request.url.includes('admin') && !options.exclude.includes(url)) {
      if (!user) {
        console.log('未登录', token);
        ctx.body = { status: 1001, data: '用户未登录' };
      } else {
        console.log('登录', token);
        await next();
      }
    } else {
      await next();
    }

  };
};

问了一下 border-radius 的理解、取值个数

他的取值和margin很像。

  • 一个值时,表示四个角的值都一样
  • 两个值时,表示对角取值相等
  • 三个值时,表示左上 右上和左下 右下
  • 四个值时,表示四个角的取值。

nodejs了解多少?跟其他的后端语言相比有什么优点、缺点

nodejs

  • 解释性语言
  • Node.js使用事件驱动的无阻塞 I/O 模型,使其轻量级且高效,非常适合跨分布式设备运行的数据密集型实时应用程序。
  • 单线程但高度可扩展 其他后台语言(java为例)
  • 编译型语言
  • Java允许独立的多线程

路由器、网桥、交换机工作在哪一层,具体干什么

  • 网桥:工作在数据链路层,在不同或相同类型的LAN之间存储并转发数据帧,必要时进行链路层上的协议转换。可连接两个或多个网络,在其中传送信息包。
  • 交换机:工作在数据链路层,原理等同于多端口网桥。作用是连接数个相同网段的不同主机,减少网内冲突,隔离冲突域。利用存储转发和过滤技术来从物理上分割网段。
  • 路由器:工作在网络层在不同的网络间存储并转发分组。可在异种网络之间(即不同类型的局域网互连,局域网与广域网,广域网与广域网)传输数据并进行路径选择,使用专门的软件协议从逻辑上对整个网络进行划分。
  • 网关:对高层协议(包括传输层及更高层次)进行转换的网间连接器。允许使用不兼容的协议,比如SPX/IPX和TCP/IP的系统和网络互连。因为协议转换是网关最重要的功能,所以答案是工作在传输层及以上层次。
  • 网卡:在物理层上网卡主要是完成物理接口的连接,电信号的传送以及将数据分解为适当大小的数据包之后向网络上发送的功能. 数据链路层功能包括链路建立和拆除,帧定界同步顺序差错控制这些。大多认为主要工作在物理层。

关系型数据库和非关系型数据库的区别

具体比较请看这里

关系型数据库

  • 关系型数据库是指采用了关系模型来组织数据的数据库。简单来说,关系模式就是二维表格模型
  • 事务的ACID, 具体请访问这里
    • 原子性(Atomicity): 事务要么全部完成,要么全部取消。 如果事务崩溃,状态回到事务之前(事务回滚)。
    • 隔离性(Isolation): 如果2个事务 T1 和 T2 同时运行,事务 T1 和 T2 最终的结果是相同的,不管 T1和T2谁先结束。
    • 持久性(Durability): 一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数据库中。
    • 一致性(Consistency): 只有合法的数据(依照关系约束和函数约束)才能写入数据库。
  • 在使用时需要事先定义字段,及其类型。
  • 关系型数据库适合存储结构化数据

非关系型数据库

  • 非关系型数据库以键值来存储,且结构不稳定,每一个元组都可以有不一样的字段,这种就不会局限于固定的结构,可以减少一些时间和空间的开销。
  • 没有事务这个概念,每一个数据集都是原子级别的。
  • 不需要事先定义字段,我们可以动态添加字段。
  • 非关系型数据库适合存储非结构化数据。

nextTick具体怎么实现的呢

将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它。他会等到组件的所有子组件也加载完毕后执行的。

使用场景:想要操作基于最新数据的生成DOM时,就将这个操作放在 nextTick 的回调中。例如计算图片加载后的高度,然后计算图片下方元素的位置。

将传入的回调函数包装成异步任务,异步任务又分微任务和宏任务,为了尽快执行所以优先选择微任务。

源码位置:core\packages\runtime-core\src\scheduler.ts image.png

进程间通信

管道

  • 管道是一种半双工的通信方式,数据只能单向流动。
  • 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)

命名管道FIFO

  • 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信
  • 它以一种特殊设备文件形式存在于文件系统中。

消息队列MessageQueue

基本原理:A 进程要给 B 进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返 回了,B 进程需要的时候再去读取数据就可以了。

  • 消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列 ID)来标识
  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  • 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
  • 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

共享存储SharedMemory

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

  • 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
  • 因为多个进程可以同时操作,所以需要进行同步。
  • 信号量 + 共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

信号量Semaphore

信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。可以用来控制多个进程对共享资源的访问。

  • 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
  • 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

套接字Socket

套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机间的进程通信。