面试整理复盘
Vue2.x和Vue3.x在响应式方面有什么区别
-
Vue2的响应式原理
在Vue2.x版本中主要是通过
Object.definiteProperty这个核心API来实现的。响应式也就是说在组件data数据一旦发生变化,立刻触发视图的更新- 缺点:
- 深度监听、需要递归到底,一次性计算量较大
- 无法监听新的属性、删除属性(需要使用Vue.set、Vue.delete可以删除)
- 无法监听原生数组,需要重写数组原型
- 缺点:
-
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已经存在对应版本的信息,则直接返回,否则重新走灰度流程
-

优点:
- 灵活、可控性强,可以根据具体的业务需求做灰度放量的规则
缺点:
-
后端对服务器有压力,需要做相关优化,针对多页面的应用会比较麻烦
-
nginx+服务端+redis+[前端SDK]
这种方案其实就是开启stable和beta两个版本的nginx服务,在运维层启动一层入口nginx服务,作为转发
- 客户端通过域名访问项目,请求灰度规则,命中后,并给客户端设置cookie作为标识,并将用户标识存放到redis,将用户重定向到指定版本
- 灰度规则接口请求时,如果已有cookie,则返回对应的版本,不存在的话,就去redis中查找对应信息,将用户进行重定向
- 前端SDK功能,用于控制灰度发布的请求时机、回调操作和其他业务操作
SDK使用场景:
在进入到项目某一页面或者首页的时候,会触发灰度功能,告知用户有内测版本,询问用户是否体验,用户点击同意之后,跳转到灰度版本

代码的话,前端需要调用一下接口,具体核心的逻辑在后端。下面代码使用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
};
});
本文来自博客园,作者:前端加油站,转载请注明原文链接:https://www.cnblogs.com/bllx/p/19706229

浙公网安备 33010602011771号