面试整理复盘

Vue2.x和Vue3.x在响应式方面有什么区别

  1. Vue2的响应式原理

    在Vue2.x版本中主要是通过Object.definiteProperty这个核心API来实现的。响应式也就是说在组件data数据一旦发生变化,立刻触发视图的更新

    • 缺点:
      • 深度监听、需要递归到底,一次性计算量较大
      • 无法监听新的属性、删除属性(需要使用Vue.set、Vue.delete可以删除)
      • 无法监听原生数组,需要重写数组原型
  2. Vue3.响应式原理

    Vue3.x 时通过Proxy来实现响应式,相比于VUE2.x

    • 深度监听,性能更好(获取到哪一层才触发响应式get,不是一次性递归)
    • 可监听新增/删除属性
    • 可监听数组变化

    下面就以代码的形式举例来说一下Vue2

    //Vue 2.x响应式原理
    function updateView(){
      console.log('视图更新了')
    }
    //重新定义数组原型
    const oldArrayProperty = Array.prototype
    const arrProto = Object.create(oldArrayProperty)
    ['push','pop','shift','unshift','splice'].forEach(methodData => {
      arrProto[methodData] = function(){
        updateView()
        oldArrayProperty[methodData].call(this,...arguments)
      }
    })
    // 重新定义属性,监听起来
    function defineReactive(target、key、value) {
      observer(value)
     // 核心API
      Object.defineProperty(target,key,{
        get(){
          return value
        }
        set(newValue){
        	if(newValue !== value){
            // 深度监听
            observer(newValue)
            value = newValue
            updateView()
          }
      	}
      })
    }
    
    //监听对象属性
    function observer(target){
      if(typeof target !== 'object' || target === null){
        return target
      }
      if(Array.isArray(target)){
        target.__proto__ = arrProto
      }
      for(let key in target){
        definReactive(target,key,target[key])
      }
    }
    

    接下来说一下vue3中的Proxy的使用

    // 创建响应式
    function reactive (target={}){
      if(typeof target !== 'object' || target === null){
        //不是数组、对象,则返回
        return target
      }
    }
    // 配置代理
    const proxyConf = {
      get(target,key,receiver){
        // 处理本身的属性
        const ownKeys = Reflect.ownKeys(target)
        if(ownKeys.includes(key)){
          console.log('get',key) // 监听
        }
        const result = Reflect.get(target,key,receiver)
        // 深度监听
        return reactive(result)
      },
      set(target,key,val,receiver){
        // 重复数据不做处理
        if(val === target[key]){
          return true
        }
        const ownKeys = Reflect.ownKeys(target)
        if(ownKeys.includes(key)){
          console.log('已有的key',key)
        } else {
          console.log('新增的key',key)
        }
      },
      deleteProperty(target,key){
        const result = Reflect.deleteProperty(target,key)
        console.log('delete property',key)
        return result
      }
    }
    

Web Worker的使用

在我们正常项目的页面中,如果在执行脚本的时候,页面的状态是不可响应式的,直到脚本执行完成后,页面才变成了响应式。Web Worker是运行在后台的JS,独立于其他脚本,不会影响页面性能,而且可以通过postMessage将结果直接回传到主线程,这样在渲染大量数据或者执行复杂业务逻辑时,就不会阻塞主线程了

首先我们使用Web Worker要注意的事项:

  • Web Worker不能使用本地文件,必须是网络上的同源文件
  • Web Worker不能进行DOM的操作,也不能获取dom对象,dom相关的东西只有主线程有,只做一些计算相关的操作
  • 有的东西没办法通过主线程传递给子线程,比如方法、dom节点、一些对象的特殊设置(freeze、getter、setter这些,vue的响应式对象无法传递)
  • 模块引入问题:目前两种引入方式:importScript('网络文件路径'),在创建Web Worker时标注类型

我们要如何使用Web Worker?

// Worker 管理器,封装 Web Worker 的使用
interface GraphData {
  nodes: Array<{
    id: string;
    name: string;
    type: string;
    x: number;
    y: number;
  }>;
  edges: Array<{
    source: string;
    target: string;
  }>;
}

type ProgressCallback = (progress: number) => void;
type CompleteCallback = (data: GraphData) => void;
type ErrorCallback = (error: Error) => void;

class WorkerManager {
  private worker: Worker | null = null;

  createWorker(): void {
    if (this.worker) {
      this.worker.terminate();
    }
    this.worker = new Worker(new URL('./dataWorker.ts', import.meta.url), {
      type: 'module',
    });
  }

  processData(
    data: any[],
    onProgress: ProgressCallback,
    onComplete: CompleteCallback,
    onError: ErrorCallback
  ): void {
    if (!this.worker) {
      this.createWorker();
    }

    if (!this.worker) {
      onError(new Error('Failed to create worker'));
      return;
    }

    this.worker.onmessage = (e: MessageEvent) => {
      const { type, progress, data: resultData } = e.data;

      switch (type) {
        case 'PROGRESS':
          onProgress(progress);
          break;
        case 'COMPLETE':
          onComplete(resultData);
          break;
        default:
          break;
      }
    };

    this.worker.onerror = (error) => {
      onError(new Error(error.message));
    };

    this.worker.postMessage({
      type: 'PROCESS_DATA',
      data,
    });
  }

  terminate(): void {
    if (this.worker) {
      this.worker.terminate();
      this.worker = null;
    }
  }
}

export default WorkerManager;

CDN 灰度发布

对于CDN的灰度发布,可以先了解一下什么是灰度发布?

将某个功能灰度发布(逐步放量)给特定上线的人群,避免新功能全量上线带来的风险。举个例子来说吧,当某个项目处在1.0版本,但是有个版本1.1在内测是没有问题的,但是这个版本的功能改动了大家常用的模块,为了避免全量上线导致大家都无法适应或者体验不好,就会提前选择一部分用户针对他们来上线这个版本的功能,这样的话,就会一部分用户在使用1.0版本,一部分用户在使用1.1版本,如果试用的用户反馈较好,且版本稳定的话,所有用户就会过度到1.1版本,如果1.1版本接到投诉的问题严重,这种情况下,就会回退到1.0版本。上述的例子就是一个很好的灰度发布的案例。

灰度发布的核心就是分流,也就是说一部分用户能看到,一部分用户看不到,所以注意核心的算法就是分流算法。

作为用户来说,我最常用的APP之一是京东金融,一般情况下,当某家银行入驻到其中时,它就会使用灰度发布,针对不同特征的用户来提前使用。收到反馈。

常见的分布方案:

  • 服务端渲染分流

    • 以单页面应用为例,前端打包好两份代码分别部署到服务器上

    • 在后台管理添加版本,其实是为了让服务器端读取单页面的index.html

    • 客户端访问服务器,服务器根据灰度规则set-cookie并在redis存储,返回对应版本的index.html

    • 二次访问服务器时,如果存在cookie且redis已经存在对应版本的信息,则直接返回,否则重新走灰度流程

image

优点:

  • 灵活、可控性强,可以根据具体的业务需求做灰度放量的规则

缺点:

  • 后端对服务器有压力,需要做相关优化,针对多页面的应用会比较麻烦

  • nginx+服务端+redis+[前端SDK]

    这种方案其实就是开启stable和beta两个版本的nginx服务,在运维层启动一层入口nginx服务,作为转发

    • 客户端通过域名访问项目,请求灰度规则,命中后,并给客户端设置cookie作为标识,并将用户标识存放到redis,将用户重定向到指定版本
    • 灰度规则接口请求时,如果已有cookie,则返回对应的版本,不存在的话,就去redis中查找对应信息,将用户进行重定向
    • 前端SDK功能,用于控制灰度发布的请求时机、回调操作和其他业务操作

    SDK使用场景:

    在进入到项目某一页面或者首页的时候,会触发灰度功能,告知用户有内测版本,询问用户是否体验,用户点击同意之后,跳转到灰度版本

image

代码的话,前端需要调用一下接口,具体核心的逻辑在后端。下面代码使用node.js+koa项目中临时写的伪代码

/ 获取版本信息
router.get('/version', async (ctx) => {
    const version = ctx.cookies.get('version');
    const uuid = ctx.query.get('uuid');
    log('版本信息:', { version, uuid });
    const uuids = ['uuid1', 'uuid2', 'uuid3']; // 模拟数据库中的UUID列表
    const redisUuids = [{id:'uuid1',version:'stable'},{id:'uuid3',version:'beta'}]; // 模拟从Redis获取的UUID列表
    let cache = false; // 模拟缓存状态
    if (version) {
        cache = true; // 模拟缓存命中
    } else {
        const uItem = uuids.find(id => id === uuid);
        if (uItem) {
            ctx.cookies.set('version', uItem.version, { httpOnly: true, maxAge: 24 * 60 * 60 * 1000 }); // 设置版本信息到cookie
        } else {
            if(uuids.includes(uuid)){
                ctx.cookies.set('version', 'beta', { httpOnly: true, maxAge: 24 * 60 * 60 * 1000 }); // 设置版本信息到cookie
            } else {
                ctx.cookies.set('version', 'stable', { httpOnly: true, maxAge: 24 * 60 * 60 * 1000 }); // 默认版本
            }
            
        }
    }
    ctx.body = {
        code: 200,
        message: '版本信息获取成功',
        data: {
            version: uuids.includes(uuid) ? 'beta' : 'stable',
        },
        ctxUrl:ctx.request.url
    };
   
});
posted @ 2026-03-12 09:39  前端加油站  阅读(2)  评论(0)    收藏  举报