【Vue】部分源码

168 阅读13分钟

1、vue的理解

  渐进式框架(中图的可选技术方案选择多样),只关注视图层,

声明式渲染  =>  组件系统 => 客户端路由 =>状态管理 =>构建工具

声明式框架、MVVM模式、虚拟dom、组件化(高内聚,低耦合)

2、spa的理解

单页面应用(spa):只有一个html页面,切换页面是通过监听路有变化,由于html中是空的html结构,无法做seo优化,所以产生首次白屏的问题。

多页面应用(mpa): 页面必须重新加载,复用组件不太方便,服务端返回的html页面,不好维护,页面跳转是需要刷新的,ssr(服务端渲染)。

seo优化的解决方案:

1⃣️静态的话可以用静态页面预渲染,打包的时候把页面放在浏览器中运行,把htmlb,比如官网(比较稳定的)中可以使用这种方案。

2⃣️ssr(首屏ssr服务端加载)+csr(客户端渲染)也就是NuxtJs去实现。

3、响应式数据的理解

响应式:用户的取值和修改都被用户检测到。

Vue2缺陷:

1⃣️ 对象内通过defineReactive方法,使用defineProperty进行数据劫持(只劫持已存在的属性),对属性进行重写加入get和set;数组则是通过重写数组方法,性能较低。全通过递归和遍历所有的属性,也导致性能变低下。

2⃣️新增和删除时(并未加入get和set   ),通过setset、delete去补充

3⃣️Es6中的Map和Set不支持

Vu3改进:  

1⃣️通过Proxy代理,并不是重写属性,而是代理拦截了属性。

2⃣️Vue2中在get中通过搜集water,而Vue3中通过get去搜集effect。3⃣️vue2和vue3都是递归,Vue3是使用时递归,而Vue2是直接递归。

4、数组的劫持过程

let newArrayProto = Object.create(Array.prototype);

Let oldArrayProto = Ob

[‘push’, ’shift’,…..].forEach(method=>{

newArrayProto[method] = function(…arg){

// 进行函数劫持 

oldArrayProtomethod

}

})

数组的缺点:数组的索引和长度是无法监控的。    

5、Vue的依赖搜集

Vue2:这个流程:

1⃣️每个属性都有自己的dep属性,存放所依赖的watcher(即存放多个dep),当属性变化后会通知watcher更新

2⃣️默认在初始化会调用render函数,此时会触发属性依赖搜集dep

3⃣️当属性发生修改时,会触发watcher更新dep.notify() 

当组件渲染的时候,会创建一个渲染watcher,渲染时调用render方法,并且会把watcher放在全局上,为了后续继续使用,开始进行页面渲染,访问响应式数据,因为响应式数据会配备dep属性,会记住哪个模板中使用了,即会记住当前的watcher,后面如果哪个属性改变了会通过dep去通知watcher,即组件重新更新了。

Vue3:这个流程:

1⃣️vue3中会通过Map结构将属性和effect映射起来

2⃣️默认在初始化会调用render函数,此时会触发属性依赖收集track

3⃣️当属性发生改变时会找到对应的effect列表依次执行trigger

6、Vue2中的Vue.set是如何实现的

总结:新增属性无法实现响应式的问题,因为defineproperty方法只会劫持已存在的属性,而且对于数组没有解决索引和长度问题;针对对象,Vue.set等价于defineproperty定义一个新的属性

函数参数是target、key、value,

数组是通过splice实现的;

对象是通过defineReactive(即defineproperty,定义响应式数据),并通知页面更新;

Vue3中已经删掉这个方法

7、v-if和v-show的实现原理

v-if:如果条件不成立,不会渲染当前指令所在节点的dom元素

v-show:只是切换dom的显示和隐藏,最终会被编译成css中的display(通过切换display:none和原来所拥有的display的值进行控制)

Vue2中:

v-if,在编译时条件满足时会创建一个div,而不满足时就会创建一个注释节点(空节点),可以阻断内部代码是否执行。

v-show,在编译时会变成一个自定义的指令。在自定义指令中的钩子:通过变量originalDisplay保留默认的el.style.display,如果这个值是none的话就就置为空;当更新update时,把这个originalDisplay赋给这个el.style.display。

Vue3中:

v-if,跟vue2原理类似

v-show,跟vue2原理类似,只是把方法分离成一个setDisplay的方法。

如果v-if和v-show同时写入,v-if还是会被编译,v-if的优先级更高。

(v-if="true" v-show="false”,会不显示出来)

8、watch和computed的区别:

vue2中三种watcher(渲染watcher、计算属性watcher、用户watcher);

vue2中三种effect(渲染effect、计算属性effect、用户effect);

Vue2中:

1⃣️computed(计算属性)是有缓存的,每一个计算属性内部维护一个dirty属性,值为true;当取值时执行用户的方法,取到值就缓存给一个value,并把dirty改为false,下一次再取值时根据dirty的false值直接返回value的值;当dirty为true时会触发更新。

1、计算属性给个计算属性watcher,给个默认的watcher一个lazy:true,不会立即执行;

2、计算属性通过一个方法变成了一个属性的原理:内部是通过defineProperty将计算属性定义到实例子上;

3、当用户取值时,触发getter,拿到计算属性对于的watcher,看dirty是否为true,如果是true就求值运算;

4、计算属性watcher中依赖的属性(即引入了别的值)去搜集模版的渲染(最外层的)watcher,通过dirty值得更新去触发页面的更新;

5、如果依赖的值没有变化,则使用缓存的值;

2⃣️watch(监听),initWatcher

底层new了一个watcher,

计算属性不支持异步逻辑。

Vue3中:

1⃣️computed(计算属性)与vue2的原理不同。computed,本身就具有收集的功能,收集组件渲染的effect,而不是像vue2中通过属性去收集外层的watcher;如果其中依赖的属性发生变化,会通知计算属性effect更新dirty,并且计算属性会触发自己收集的渲染effect执行。

2⃣️ doWatch方法,底层new了一个ReactiveEffect,多了个onCleanup函数,去让用户做一些清理操作(比如input框中,快速输入12,第一次接口响应比较慢2s,第二次接口响应会快1s,vue2中会通过慢的接口去覆盖,先显示12,再显示1;但是在vue3中可以多了第三个参数onCleanup默认的清理方法,可以拦截上一次存入的cleanup函数,在这个函数中更改状态上个锁,就直接显示12了,即在第二次监听时根据cleanup函数里更改的状态取消了之前未完成的请求)

9、vue3中的ref和reactive的区别:

reactive:只用于处理对象类型的数据响应式,底层采用new Proxy()

ref:通常用于处理单值的响应式,主要解决原始值的响应式问题,底层采用的是类似Objecet.defineProperty()方法实现的

reactive,主要是通过createReactiveObject,先判断传入的值是否是对象,不是对象的话,不做任何操作直接返回。如果是对象类型的数据,最后会通过new Proxy()做一个代理,返回一个代理对象。

ref的实现,传递一个任意类型的值,如果是ref不做处理,如果不是就通过new RefImpl()来创建一个ref,如果是对象,会通过reactive方法做处理,ref包对象,reactive可以包ref,reactive可以默认做一个拆包的操作,不需.value取值

9、vue3中的watch和watchEffect的区别:

watchEffect:立即运行一个函数,然后被动的追踪依赖,当依赖改变时重新执行函数。

Watch:侦听一个或多个响应式数据源,在变化时掉用这个函数

都会创建一个响应式函数,new ReactiveEffect(getter,scheduler),在watchEffect中当数据变化时,会调用scheduler,内部会触发effect.run()方法。而watch是数据变化后,会调用scheduler,内部会调用cb回调函数。

effect.run()

10、如何将template转化为render函数

1、将template模版转化成ast语法树

2、对静态语法做标记,对静态节点进行跳过diff操作

3、重新生成代码(在运行时,通过Vue.compile(‘<div><div>’))

11、new Vue()这个过程做了什么

1、内部会进行初始化操作

2、内部会初始化组件绑定的事件(initEvent),初始化父子关系(initLifecycle)

3、初始化响应式数据data、computed、props、watch、method。对provide和inject进行处理( initInjections和initProvide),也对数据进行劫持,对象采用defineProperty数组采用方法重写(initData)

4、内部挂载时会产生一个watcher,会调用render函数触发依赖搜集,内部会所有的响应式数据增加dep属性,让属性记录当前的watcher

5、vue更新的时候采用虚拟dom的方式进行diff算法更新。

12、Vue.observable()的原理

普通对象变成响应式对象,对数据劫持。类似pinia和vuex,不复杂时可以使用

13、v-if和v-for的区别

vue3:v-if的优先级高 ,不能在一起使用,创建一个template包裹

vue2:v-for的优先级高 编码时是:先解析v-for,再解析v-if。会导致先循环在判断,浪费性能,会把v-if判断掉的节点转化为一个注释节点

14、Vue的diff算法原理

核心:比较两个平级额度虚拟节点的差异,不考虑跨级比较的情况,内部采用深度递归的方式进行比较。

Vue2:

比较流程:

1、先比较是否是相同的节点

2、相同节点就比较属性,并将老的虚拟dom复用给新的虚拟节点

3、比较儿子节点,考虑老节点和新节点儿子的情况

4、优化比较,头头、尾尾、头尾、尾头,双指针的形式去遍历

5、对比查找进行复用

15、key的作用和原理

1、主要用在虚拟dom算法,用于比对新旧节点,如果不用key,会产生“就地修改”的问题

2、在使用v-for时,用index作为key在新增时,也会遇到“就地修改”的问题

一般来说,标签和key来区别是否时相同的节点,

16、Vue.use()是什么

概念:

安装vue.js的插件,如果是对象写法,必须提供install方法,如果是函数写法,会被作为instal

L方法。install方法调用时,会将Vue作为参数传入,这样插件中就不再需要依赖Vue了。

功能:

1、添加全局指令、全局过滤器,全局组件

2、全局混入

3、通过Vue.prototype添加vue实例方法

17、Vue.extend()方法的作用

参数是template和data,最后可以通过new来创建一个子类  ,vue内部的组件都是通过extend()来创建的  

18、Vue中组件的data为什么是函数

Vue2:

1、根实例(new Vue()的实例对象)是单例的不涉及到共享,无论是对象和函数都可

2、防止多个组件实例对象公用一个dara,产生数据污染,所以需要工厂函数返回一个全新的data作为组件的数据源。

Vue是通过  

Vue3是通过组件的形式

Vue2版本是命令方式使用,通过new Vue去创建Vue实例

Vue3版本将命令和功能API函数化,通过createApp去创建应用实例,Vue.createApp函数功能就是返回应用实例对象

19、函数式组件的优势

函数式组件:无状态、无生命周期、无this,但是相对性能较高,函数式组件就是普通的函数,没有new的过程

在vue2中是有优势的,在vue3中没有优势

20、v-once

内置指令,只渲染一次,随后被视为静态内容被跳过,以优化更新性能,本质上是通过缓存来实现

Vue3.2后,加入了v-memo=“[a, b]”

21、Vue.mixin的原理

1⃣️、替换,如:props、methods、computed、inject(比如,组件的props中的name会替换mixin中的name)

2⃣️、合并,如:data、provide;watch、生命周期合并成队列(比如,data的合并,以组件本生为主,它的权重更大)

3⃣️、叠加,如:component, directive, filter,会在原型链上叠加(比如,filter的钩子,会在原型链上一层一层叠加)

核心策略:

默认策略,defaultStrats函数中(用三目预算符,优先使用组件的options),组件的options   >  组件mixin options  >  全局options。通过mergeOptions、mergeAssets(如:component, directive, filter等)把全局属性进行合并,会进行判断子类的mixins进行递归合并,又如mergeWatch等很多合并操作。

22、Vue中的slot是如何实现的

定制化组件,拓展组件的功能,模版编译后进行标记,比如返回name的名字,attrs、slot的值放入一个函数中,根据映射表的映射关系,来渲染以替换占位符。

23、双向绑定的理解和原理

双向绑定靠v-model,数据和视图的修改,默认是:value+input的语法糖,

在不同的表单元素中解析的不一样,比如复选框会被解析成change事件

24、.sync的

v-model默认只能双向绑定一个属性,而.sync可以绑定多个变量。Vue3把.sync删除了,v-model:可以传多个

25、name选项的好处

1、比如递归组件中,可以通过name属性找到这个组件进行递归

2、在keep-live中通过name属性,中的include和exclude去筛选name

3、查找某个组件$children可以查找这个组件,去查找和调试

26、Vue异步组件的作用和原理

1、回调写法。

2、通过import导入,默认就是promise

3、对象写法,可以loading的选择,失败的时候渲染一个错误组件

原理:(与图片懒加载类类似)

默认渲染异步占位符的节点

组件加载完毕后调用$forceUpdate强制更新,渲染加载完毕后的组件

27、nextTick的理解

Vue2:

数据同步修改,视图异步更新的。

nextTick函数, 内部既有同步代码又有异步代码

nextTick 回调,可以理解为 99% 的场景下都是微任务

优雅降级,Promise.then**、MutationObserver 、 **setImmediate和setTimeout(fn, 0)优雅降级的过程

Vue3:

不考虑兼容性,直接使用的是Promise.then,变成了一个方法。实际底层更新的时候也是nextTick的策略,跟上面nextTick说的一样。


28、keep-alive的原理:

路由页面的渲染:使用可以在路由meta属性中加入,

动态组件:keep-alive标签和router-view标签搭配使用缓存组件

29、Vue的性能优化

1、数据嵌套层级过深,合理设置数据 .....

30、

减小文件体积,

1、路由懒加载,大组件用异步组件,以减少入口文件的体积

2、提取功共代码

3、静态资源缓存,本地存储

4、图片压缩,图片合并(雪碧图)

5、打包时开启gzip压缩,使用插件

6、使用ssr对首屏做服务端渲染

31、跨域问题

1、cors,由服务端设置,允许指定的客户端访问

2、构建工具设置反向代理,比如webpack中devServe设置proxy;或者上线时nginx做反向代理

3、websocket通信

….

32、Vue封装axios的细节

1、设置请求超时时间

2、根据项目设置环境变量设置请求路径

3、设置请求拦截,如添加token

4、设置响应拦截,对响应的状态码或数据进行格式化

5、添加请求loading效果,