欢迎来到皮皮网网站!

【淘宝模板源码下载】【qos 源码】【cycles源码】vuerouter源码分析

时间:2024-12-22 20:49:14 来源:开源商城源码出售

1.vue-router源码学习 - install与<router-view>
2.Vue Router 源码学习笔记4 - pushState和replaceState的码分实现
3.Vue Router 源码学习笔记5 - 视图更新的实现
4.keep-alive的vue2和vue3的源码以及LRU算法

vuerouter源码分析

vue-router源码学习 - install与<router-view>

       本文深入解析Vue-router的install过程和部分逻辑。首先,码分探讨Vue-router的码分注册机制,即Vue.use(VueRouter)时的码分执行关键代码。利用Vue.mixin功能,码分混入beforeCreate钩子,码分淘宝模板源码下载确保所有组件在初始化阶段定义好_router和_routerRoot。码分this.$options展示组件构造时传递的码分选项信息。根组件执行beforeCreate时,码分_routerRoot指向根组件,码分而非根组件的码分执行则不同。全局混入后,码分定义$router和$route变量,码分并注册两个组件。码分

       接下来,码分聚焦渲染流程的核心。主要负责渲染匹配到的路由组件。上篇中介绍的qos 源码嵌套路由机制在匹配RouteRecord后,使用Route,其matched字段包含匹配的RouteRecord及其所有祖先RouteRecord。多个层级的页面中,每个router-view需知道自己的层级,通过源码内容实现。每个router-view标记自身,便于确定层级,在找到对应层级组件后进行渲染。

       至此,渲染过程简化流程清晰呈现,但Vue-router的复杂性意味着仍有更多细节待探索。后续文章将继续深入,逐步解析更多功能。

Vue Router 源码学习笔记4 - pushState和replaceState的实现

       在Vue Router中,HTML5History的push和replace操作主要通过util/push-state.js中的相应函数来执行,它们依赖window.history.pushState和window.history.replaceState API。对于HTML5History,cycles源码如果浏览器支持,就按照标准流程进行,即利用pushState或replaceState改变浏览器的历史记录,而不会导致页面刷新。

       对于HashHistory,浏览器支持与否对操作方式有影响。若支持,同样采用类似方法,通过pushState设置hash部分,replaceState则调用window.location.replace替换当前URL。然而,如果浏览器不支持pushState,会直接操作window.location更改URL,以#符号为标志。

       MDN文档中提到,pushState需要三个参数:状态对象、标题(通常忽略)和可选的dayjs源码URL。而replaceState与pushState类似,只是替换当前历史项,而非新增,尽管它会在浏览器历史中生成新的记录。

       当路由更改后,紧接着是视图的同步更新。详细了解这两个方法的使用,可以参考MDN文档:developer.mozilla.org/zh-CN/docs/Web/API/History/pushState。

       继续深入学习,确保在实际项目中正确运用这些原理,实现无缝的路由切换。

Vue Router 源码学习笔记5 - 视图更新的实现

       History模块的updateRoute方法主要执行三项关键任务。首先,此方法触发了cb函数,该函数相当于注册监听器,这一过程在VueRouter初始化(src/index.js)阶段完成。其次,hutool 源码更新了内部的_route属性。那么,视图为何会据此更新呢?答案在于响应式属性的机制。

       VueRouter的install方法(src/install.js)对全局Vue对象进行了扩展,其中将_route属性定义为响应式属性。这意味着每当_route属性变化,视图就会自动更新。

       响应式属性的实现原理基于Object.defineProperty,这是一种JavaScript对象属性的动态绑定机制。通过它,可以为对象属性添加读取和写入操作的监听逻辑,从而实现在属性值改变时触发相应的响应行为。

       从设计模式的角度来看,这里采用了发布订阅模式。发布者(即属性值)在变化时发出事件,订阅者(视图)接收到事件后执行相应的更新操作。

       进一步探索,可以尝试实现类似Object.defineProperty的功能,以深入理解其工作原理和在Vue中响应式系统中的应用。

keep-alive的vue2和vue3的源码以及LRU算法

       0.LRU算法

       LRU(leastrecentlyused)根据数据的历史记录来淘汰数据,重点在于保护最近被访问/使用过的数据,淘汰现阶段最久未被访问的数据

       LRU的主体思想在于:如果数据最近被访问过,那么将来被访问的几率也更高

       经典的LRU实现一般采用双向链表+Hash表。借助Hash表来通过key快速映射到对应的链表节点,然后进行插入和删除操作。这样既解决了hash表无固定顺序的缺点,又解决了链表查找慢的缺点。

       但实际上在js中无需这样实现,可以参考文章第三部分。先看vue的keep-alive实现。

1.keep-alive

       keep-alive是vue中的内置组件,使用KeepAlive后,被包裹的组件在经过第一次渲染后的vnode会被缓存起来,然后再下一次再次渲染该组件的时候,直接从缓存中拿到对应的vnode进行渲染,并不需要再走一次组件初始化,render和patch等一系列流程,减少了script的执行时间,性能更好。

       使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive

       当我们从首页–>列表页–>商详页–>再返回,这时候列表页应该是需要keep-alive

       从首页–>列表页–>商详页–>返回到列表页(需要缓存)–>返回到首页(需要缓存)–>再次进入列表页(不需要缓存),这时候可以按需来控制页面的keep-alive

       在路由中设置keepAlive属性判断是否需要缓存。

2.vue2的实现

       实现原理:通过keep-alive组件插槽,获取第一个子节点。根据include、exclude判断是否需要缓存,通过组件的key,判断是否命中缓存。利用LRU算法,更新缓存以及对应的keys数组。根据max控制缓存的最大组件数量。

       先看vue2的实现:

exportdefault{ name:'keep-alive',abstract:true,props:{ include:patternTypes,exclude:patternTypes,max:[String,Number]},created(){ this.cache=Object.create(null)this.keys=[]},destroyed(){ for(constkeyinthis.cache){ pruneCacheEntry(this.cache,key,this.keys)}},mounted(){ this.$watch('include',val=>{ pruneCache(this,name=>matches(val,name))})this.$watch('exclude',val=>{ pruneCache(this,name=>!matches(val,name))})},render(){ constslot=this.$slots.defaultconstvnode:VNode=getFirstComponentChild(slot)constcomponentOptions:?VNodeComponentOptions=vnode&&vnode.componentOptionsif(componentOptions){ //checkpatternconstname:?string=getComponentName(componentOptions)const{ include,exclude}=thisif(//notincluded(include&&(!name||!matches(include,name)))||//excluded(exclude&&name&&matches(exclude,name))){ returnvnode}const{ cache,keys}=thisconstkey:?string=vnode.key==null?componentOptions.Ctor.cid+(componentOptions.tag?`::${ componentOptions.tag}`:''):vnode.keyif(cache[key]){ vnode.componentInstance=cache[key].componentInstance//makecurrentkeyfreshestremove(keys,key)keys.push(key)}else{ cache[key]=vnodekeys.push(key)//pruneoldestentryif(this.max&&keys.length>parseInt(this.max)){ pruneCacheEntry(cache,keys[0],keys,this._vnode)}}vnode.data.keepAlive=true}returnvnode||(slot&&slot[0])}}

       可以看到<keep-alive>组件的实现也是一个对象,注意它有一个属性abstract为true,是一个抽象组件,它在组件实例建立父子关系的时候会被忽略,发生在initLifecycle的过程中:

//忽略抽象组件letparent=options.parentif(parent&&!options.abstract){ while(parent.$options.abstract&&parent.$parent){ parent=parent.$parent}parent.$children.push(vm)}vm.$parent=parent

       然后在?created?钩子里定义了?this.cache?和?this.keys,用来缓存已经创建过的?vnode。

       <keep-alive>直接实现了render函数,执行<keep-alive>组件渲染的时候,就会执行到这个render函数,接下来我们分析一下它的实现。

       首先通过插槽获取第一个子元素的vnode:

constslot=this.$slots.defaultconstvnode:VNode=getFirstComponentChild(slot)

       <keep-alive>只处理第一个子元素,所以一般和它搭配使用的有component动态组件或者是router-view。

       然后又判断了当前组件的名称和include、exclude(白名单、黑名单)的关系:

//checkpatternconstname:?string=getComponentName(componentOptions)const{ include,exclude}=thisif(//notincluded(include&&(!name||!matches(include,name)))||//excluded(exclude&&name&&matches(exclude,name))){ returnvnode}functionmatches(pattern:string|RegExp|Array<string>,name:string):boolean{ if(Array.isArray(pattern)){ returnpattern.indexOf(name)>-1}elseif(typeofpattern==='string'){ returnpattern.split(',').indexOf(name)>-1}elseif(isRegExp(pattern)){ returnpattern.test(name)}returnfalse}

       组件名如果不满足条件,那么就直接返回这个组件的vnode,否则的话走下一步缓存:

const{ cache,keys}=thisconstkey:?string=vnode.key==null?componentOptions.Ctor.cid+(componentOptions.tag?`::${ componentOptions.tag}`:''):vnode.keyif(cache[key]){ vnode.componentInstance=cache[key].componentInstance//makecurrentkeyfreshestremove(keys,key)keys.push(key)}else{ cache[key]=vnodekeys.push(key)//pruneoldestentryif(this.max&&keys.length>parseInt(this.max)){ pruneCacheEntry(cache,keys[0],keys,this._vnode)}}

       如果命中缓存,则直接从缓存中拿vnode的组件实例,并且重新调整了key的顺序放在了最后一个;否则把vnode设置进缓存,如果配置了max并且缓存的长度超过了this.max,还要从缓存中删除第一个。

       这里的实现有一个问题:判断是否超过最大容量应该放在put操作前。为什么呢?我们设置一个缓存队列,都已经满了你还塞进来?最好先删一个才能塞进来新的。

       继续看删除缓存的实现:

functionpruneCacheEntry(cache:VNodeCache,key:string,keys:Array<string>,current?:VNode){ constcached=cache[key]if(cached&&(!current||cached.tag!==current.tag)){ cached.componentInstance.$destroy()}cache[key]=nullremove(keys,key)}

       除了从缓存中删除外,还要判断如果要删除的缓存的组件tag不是当前渲染组件tag,则执行删除缓存的组件实例的$destroy方法。

       ————————————

       可以发现,vue实现LRU算法是通过Array+Object,数组用来记录缓存顺序,Object用来模仿Map的功能进行vnode的缓存(created钩子里定义的this.cache和this.keys)

2.vue3的实现

       vue3实现思路基本和vue2类似,这里不再赘述。主要看LRU算法的实现。

       vue3通过set+map实现LRU算法:

constcache:Cache=newMap()constkeys:Keys=newSet()

       并且在判断是否超过缓存容量时的实现比较巧妙:

if(max&&keys.size>parseInt(maxasstring,)){ pruneCacheEntry(keys.values().next().value)}

       这里巧妙的利用Set是可迭代对象的特点,通过keys.value()获得包含keys中所有key的可迭代对象,并通过next().value获得第一个元素,然后进行删除。

3.借助vue3的思路实现LRU算法

       Leetcode题目——LRU缓存

varLRUCache=function(capacity){ this.map=newMap();this.capacity=capacity;};LRUCache.prototype.get=function(key){ if(this.map.has(key)){ letvalue=this.map.get(key);//删除后,再set,相当于更新到map最后一位this.map.delete(key);this.map.set(key,value);returnvalue;}return-1;};LRUCache.prototype.put=function(key,value){ //如果已经存在,那就要更新,即先删了再进行后面的setif(this.map.has(key)){ this.map.delete(key);}else{ //如果map中不存在,要先判断是否超过最大容量if(this.map.size===this.capacity){ this.map.delete(this.map.keys().next().value);}}this.map.set(key,value);};

       这里我们直接通过Map来就可以直接实现了。

       而keep-alive的实现因为缓存的内容是vnode,直接操作Map中缓存的位置代价较大,而采用Set/Array来记录缓存的key来模拟缓存顺序。

       参考:

       LRU缓存-keep-alive实现原理

       带你手撸LRU算法

       Vue.js技术揭秘

原文;/post/

更多相关资讯请点击【探索】频道>>>