Vue基础整理与理解

1,855 阅读9分钟

基础:

v-if和v-show 区别:

v-show:通过display来控制显示和隐藏;

v-if是组件真正的渲染和销毁,而不是现实和吟唱;

频繁切换场景使用v-show;

keep-alive:

缓存组件,需要频繁切换但不需要频繁渲染常用;例如tab切换;

也可以作为vue性能优化手段之一;

mixin:

优点:适用于 多个组件有相同的逻辑,可以使用mixin,比较方便;

缺点:多mixin可能造成命名冲突;变量来源不明确,不利于阅读;

mixin和组件可能出现多对多关系,复杂度比较高;

computed 和watch区别

**computed优点:缓存,data不变不会重新计算;提升性能;
**

watch: 观察作用,数据变化时异步回调操作;

  • computed:是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
  • watch:没有缓存性,更多的是「观察」的作用,当监听的数据变化时都会执行回调进行后续操作;当需要深度监听对象中的属性时,可以打开deep:true选项,方便对象中的每一项进行监听
  • 运用场景
    • 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
    • 当我们需要在数据变化时执行异步或开销较大的操作时,Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的,限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

$nextTick:

$nextTick在Dom更新完成之后,触发回调;

实现原理:

nextTick 方法主要是使用了宏任务和微任务,定义了一个异步方法.多次调用 nextTick 会将方法存入 队列中,通过这个异步方法清空当前队列。 所以这个 nextTick 方法就是异步方法;

在下次 DOM 更新循环结束之后执行延迟回调。nextTick主要使用了宏任务和微任务。根据执行环境分别尝试采用

  • Promise
  • MutationObserver
  • setImmediate
  • 如果以上都不行则采用setTimeout

备注: MutationObserver()

创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用。

setImmediate()方法用于中断长时间运行的操作,并在浏览器完成其他操作(如事件和显示更新)后立即运行回调函数。

定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。

v-for中用key:

1)必须是key,不能是index;

2)diff算法中通过tag和key来判断,是不是sameNode;

key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速

  • 更准确:因为带 key 就不是就地复用了,在 sameNode 函数a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。
  • 更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快

3)减少渲染次数,提升性能;

vue中常用的组件通讯;

常用的:

父子组件: props和 this.$emit;

自定义事件: event.on,event.on,event.on,event.off 和 event.no(需要在destory生命周期中,销毁自定义事件evenet.no (需要在destory 生命周期中,销毁自定义事件evenet.no(需要在destory生命周期中,销毁自定义事件evenet.off)

vuex

总共大致有这几种:

父子组件通信

  • 事件机制(**父->子props,子->父 on、on、on、emit)
  • 获取父子组件实例 parent、parent、parent、children
  • Ref 获取实例的方式调用组件的属性或者方法
  • Provide、inject (不推荐使用,组件库时很常用)

兄弟组件通信

  • eventBus 这种方法通过一个空的 Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件;

Vue.prototype.$bus = new Vue

  • Vuex

跨级组件通信

  • Vuex

  • attrs、attrs、attrs、listeners

beforeDetory 使用场景:

解除自定义事件evenet.$off

清楚定时器

解除自定义DOM 事件,例如window scroll等等;

模板编译

模板编译通过一种方式生成render函数,执行render函数返回VNode,基于vnode再执行path和diff;模板一定是转化为js代码过程,叫做编译模板;

模板不是html,html是标签语言;

例如列表模板:

// 列表const template = `  <ul>  
          <li v-for="item in list" :key="item.id">{{iitem.title}}</li> 
 </ul>`

模板编译成 render函数,用的with语法

给一个数组list遍历数组或者对象,给一个function函数, return返回li标签
//with(this) {return _c('ul',_l((list),function(item){
        return _c('li',{key:item.id}[_v(_s(item.title))])}),0)};
 其中_c代表 createElemet
 _l(列表)代表 target._l = renderList 
复制代码

vue组件中可以用render代替template;

vue组件如何渲染和更新的?

大致可以分为 初次渲染,更新过程,异步渲染 这三步骤;

初次渲染:

1)解析模板为render函数(或在开发环境已完成,vue-loader)

2)触发响应式,监听data属性getter setter;

3)执行render函数,生成vnode,patch(elem,vnode)

更新过程话:

1)修改data,触发setter(getter之前已经被监听到;若是模板中没有用到data中某个变量,则不会触发get)

2)重新执行render函数,生成newVnode

3)然后再进行patch(vnode,newVnode) (patch中的diff算法会计算出最小差异更新到dom上)

异步渲染话:

组件的异步渲染非常重要,减少DOM操作次数,可以缓解浏览器压力,主要是提升性能

主要是$nextTick异步回调,DOM渲染完,再回调;多次data修改,只会渲染一次;(nextTick原理要会)

以下内容都是自己理解(小白啊,若有不当敬请指出):

vue如何监听数据变化?Object.defineProperty响应式原理

核心就是Object.defineProperty来实现响应式(当然vue3.0使用的是Proxy)

大致分为 监听对象情况, 需要深度监听复杂对象(observer(value))情况,监听数组这几种情况;

监听对象核心:

function defineReactive(target,key,value) {  
// 核心 api 
//深度监听
observer(value)
 Object.defineProperty(target,key, {  
     get() {  
       return value    
     },  
    set(newValue) {  
      if(newValue !== value) {    
       //深度监听新值
      observer(newValue)
        // 设置新值     
         value = newValue     
        // 触发更新视图     
       updateView()    
      }  
    } 
 })}
复制代码

需要深度监听复杂对象(observer(value))情况

//如果是复杂对象,需要调用obser方法// 监听对象
function oberver(target) { 
   if (typeof target !== 'object' || target === null) {   
 // 不是对象 或数组   
 return target  }  
// 污染全局的Array 原型  
if(Array.isArray(target)) {  
   target._proto_ =  arrProto  } 
  // 重新定义各个属性  
for (let key in target) {  
  defineReactive(target,key,target[key])  
}}
复制代码

监听数组变化:(无法原生监听到数组,需要此处特殊处理;)

// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
    arrProto[methodName] = function () {
        updateView() // 触发视图更新
        oldArrayProperty[methodName].call(this, ...arguments)
        // Array.prototype.push.call(this, ...arguments)
    }
})
复制代码

object.defineProperty 缺点:

1)深度监听,需要递归到底(源数据对象有多深,就要一次性递循环归到最深处),一次性计算量比较大

2)无法监听新增属性/删除属性(可以通过Vue.set Vue.delete增删)

3)无法原生监听到数组,需要特殊处理;

diff 算法过程(未完):

这个我是看博客看了几篇,还是没明白;后来花了快一天时间才明白;建议自己手本子上画diff比对过程;

diff即对比,是一个广泛的概念。**diff算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom。**换句话说:diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁;

比较过程

  • 同级比较,再比较子节点

  • 先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)

  • 比较都有子节点的情况(核心diff)

  • 递归比较子节点;

Vue2的核心Diff算法采用了_**双端比较**_的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。(双端比较,就是同时从新旧 children 的两端开始进行比较的一种方式)
diff算法中的  双端比较大致步骤,再具体每个比对步骤建议另看专门文章,更好理解双端比较逻辑。

双端比较了,在一次比较过程中,最多需要进行四次比较:

  • 1、使用旧 children 的头一个 VNode 与新 children 的头一个 VNode 比对,即 oldStartVNodenewStartVNode 比较对。
  • 2、使用旧 children 的最后一个 VNode 与新 children 的最后一个 VNode 比对,即 oldEndVNodenewEndVNode 比对。
  • 3、使用旧 children 的头一个 VNode 与新 children 的最后一个 VNode 比对,即 oldStartVNodenewEndVNode 比对。
  • 4、使用旧 children 的最后一个 VNode 与新 children 的头一个 VNode 比对,即 oldEndVNodenewStartVNode 比对。

在如上四步比对过程中,试图去寻找可复用的节点,即拥有相同 key 值的节点。这四步比对中,在任何一步中寻找到了可复用节点,则会停止后续的步骤,可以用下图来描述在一次比对过程中的四个步骤:

diff算法时间复杂度

O(n)

正常Diff两个树的时间复杂度是O(n^3),但实际情况下我们很少会进行跨层级的移动DOM,所以Vue将Diff进行了优化,从O(n^3) -> O(n),只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。

树diff O(n3) :

包含了三部 第一: 遍历tree1;第二遍历tree2;第三,排序

vue中diff优化到O(n):

只比较同一层级,不跨级比较

tag不相同,则直接删除重建,不再深度比较

tag和key,两者都相同,则认为是相同节点,不再深度比较;

V3跟V2对比优势

性能更好

更好的ts支持

更好的代码组织

更好的逻辑抽离

体积更小

更多新功能

V3.0 proxy响应式原理

vue3使用proxy代替Ojject.defineProperty

proxy实现响应式优点:

可以实现深度监听,性能更好;

可监听 新增/删除  属性;

可直接监听数组变化;

proxy如何实现响应式监听的呢?(可参考下面代码,看代码比较好理解)

  • 判断当前的返回值是否为Object或者数组,如果是,则再通过做代理方法进行逻辑处理, 实现深度监听是在代理中的get方法调用了递归,实现了深度监听;这也是v3中proxy响应式比v2响应式性能好的原因,v3是在set方法中调用递归实现深度监听,对于data中多层嵌套结构,set获取到哪一层才递归到具体哪一层数据结构(用不到的就不做处理);而在v2 object.defineProperty响应式处理中,是一开始就递归响应式的,默认一次性全部递归了;

  • 监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?

  • 我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。也就是为了防止数据多次触发,需要做个判断旧值与新值是否相等,相等直接返回true;

怎么判断是不是新增的数据?

  • 需要在Reflect.ownKeys(target)中看是否已经有这个key了;

Vite是什么?

vite是一个前端打包工具,vue作者发起项目

发展比较快,和webpack竞争

优势:开发环境下无需打包,启动快

vite为何启动快?

开发环境使用es6 module,无需打包---就非常快

生产环境使用rollup,并不会快很多;

vue 和 react 对比;

同:

  • 都支持组件化;
  • 都支持数据驱动视图;
  • 都是用vdome操作DOM;

异:

  • react使用jsx倾向于一切都js;vue使用模板,倾向于html;
  • react函数式编程;vue声明式编程;
  • react更多要自理更生(创建了一个更分散的生态系统,所以有更丰富的生态系统),vue生态配套比较全,Vue 的路由库和状态管理库都是由官方维护支持且与核心库同步更新的;