vue2.x反思与总结(持续迭代更新……)

586 阅读5分钟

总结vue学习过程中遇到的一些问题,反思并归纳为问答形式,以增强记忆。

1.Q: 侦听属性watch与计算属性computed什么区别?

A:
语境差异:前者是监听响应式数据,进而执行一系列运算,用于一个数据影响多个数据的场景;后者是计算一个全新的属性,当这个属性所依赖的属性发生变化时,进行计算,用于一个属性依赖多个属性。
缓存性:前者不具有缓存性;后者具有缓存行,即只有当所依赖的的属性发生变化时,才会重新进行计算。
通用性:前者通过options提供了一个更为通用的解决方案,更加适合用于进行异步或者较大开销的操作。

watch:{
    immediate:false,
    deep:false,
    handler(newVal,oldVal){
        //TODO
    }
}

通常情况下使用后者,简洁而且具有缓存性。可以通过get和set满足大部分需求。

2. Q: 单向数据流和双向数据绑定有什么联系和区别?

A:
含义
单向数据流指的是数据流沿着一个方向传输,并不会逆向传输。例如props会将数据从父级传输到子级,但是子级数据变化时并不会通过props影响到父级。
双向数据绑定是指数据发生变化时会实时响应到视图层,同时视图层发生数据变化时会反映到数据层的数据变化。例如v-model。
特点/场景:非UI组件采用单向数据流,双向数据绑定仅限于UI组件。两者典型场景分别是vuex、v-model。
联系:双向数据绑定本质上是 单向数据流 + 事件监听。v-model就是vue框架内置的双向数据绑定,可视为一个语法糖。对比典型的单向数据流案例vuex,其两者本质上是相同的,都是基于事件机制。vuex直白的讲,其数据管理就是基于单向数据流,本质上是维护了一个单例的事件分发器,数据流按照规定流动,最终通过vue非侵入数据响应式完成后续的渲染与页面更新。
优缺点
单项数据流
优点:

  1. 只有一份数据且集中管理,唯一的数据入口与出口,便于数据维护。
  2. 数据发生变化时,会将数据变化反映到视图上,但是视图上的变化不能主动反映到数据层。
  3. 所有的状态可记录、观察,便于追溯。
    缺点:
  4. 对于简单的应用,单向数据流显着代码量巨大且流转过程长。
  5. 对于表单等需要局部频繁进行及时响应的操作显得臃肿且繁琐。
  6. 对于html部分已生成的代码,可能每次进行更新时都需要重新生成html结构。
    双向数据绑定
    优点:
  7. 对于需要进行局部快速响应显示的表单结构十分友好,所见即所得。
  8. 框架内置v-model,开发无感知且使用简洁。 缺点:
  9. v-model双向数据绑定是vue框架内部处理,外部无感知,跟踪存在一定困难。
  10. 改变数据的来源可能不止一个,难以管控。譬如其他组件通过refs或者自定义指令操控。

3.Q:v-for与v-if优先级问题?

A:
结论:v-for优先级高。
实践优化:在同一元素同时使用则会造成性能浪费。推荐讲v-if提升至v-for的父级元素。
进阶优化:常见场景“若是待渲染数据是对象数组,各自带有显隐控制标识,如何处理?”------->>通过计算,筛选数组,避免两者同时使用。
深度理解:源码中codegen/index.js中,优先判断了for再判断if。简单的来讲,可以通过在同时使用两者的组件中输出查看vm.$options.render,可以看到先_l进行list列表的循环渲染,然后在其中进行的if判断是否需要渲染,当不需要时才执行_e这个空函数。

4.Q:组件的data为什么要写成函数形式,而根实例则没有此限制?

A:
结论:若组件的data属性是对象,则每个组件实例的data都是对组件模板data选项的引用,这将造成数据的交叉污染,写成函数返回独立的对象则可以避免这一点。根实例是vue的单实例,不存在数据交叉污染的场景。即便是存在多个vue实例,也无非是多个vue单实例罢了。
深度理解:源码中,initData有这样一个判断--->>判断data是否是一个函数,如果是函数则执行之,并将其结果作为data选项的值。否则直接获取用户赋值的data||{}。此处即可明了数据的使用与引用状况。
进阶:组件与根实例的data检测是怎样的?---->>在options.js中,对data进行检测时,会根据传参是否有vm实例来判断是否需要对data进行格式检测。

5.Q:key的作用及原理是什么?

A:在v-for循环的时候设置key的主要目的是高效的更新虚拟DOM。其原理是在patch.js中updateChildren的sameVnode方法中通过key来判断两个节点是否是同一个节点,提高了patch的效率,减少了dom操作,避免无意义的频繁更新的同时也提高了更新效率。
拓展:如果不使用key值,可能会产生bug,如相同标签多次使用的动画,可能直接使用了缓存而不会触发动画效果。

6.Q:怎么理解vue中的diff算法?

A:

    1. 概念:diff算法是虚拟dom技术的必然产物。对vnode新老节点比较,将变化的数据更新到真实的dom上;另外,使用diff算法能高效执行对比,从而可以降低树形结构时间复杂度。
    1. 必要性:每个组件在实例化的时候,都会调用lifecycle中的mountComponent方法,此方法会生成一个watcher,若当前组件中的data选项中有多个(使用key的)数据,由于watcher粒度的降低,很难精准的找到变化的位置。
    1. 执行:
      • patch中的patchVnode是diff算法开始的地方。策略采用“深度优先,同层比较”。
      • 当修改一个数据时,会触发这个响应式数据的setter,从而触发通知,将对应的watcher加入到异步队列中,在本轮主线程任务完成后,会清空异步队列,此时会调用watcher的更新函数,他会调用组件的更新和渲染函数也就是diff执行的时刻。
      • 在patchVnode中,会对比每个新老节点。
      若新节点没有定义文本节点,则判断新老节点:
         若都有子节点,判断是否相同,   
               不同的话则进入到updateChildren。  
         若新节点有子节点,老节点有文本节点则清空老节点内容,添加新节点的子节点。  
         若老节点有子节点,移除子节点。  
         若老节点有文本节点,移除文本节点。  
      若新节点有文本节点且与老节点的文本节点不同,则直接更新文本的节点。
      
      
    1. 高效:核心是updateChildren,其比对过程是依次os与ns对比、oe与ne对比、os与ne对比、oe与ns对比。若上述对比不成立,则进行遍历查找,存在则进行修改更新否则就新增元素。当上述执行完毕,新旧vnode依旧有一方剩余,则根据情况进行批量增加或者删除。
    注:o->oldch旧;n->new新;s->start首部节点;e->end尾部节点。
    
    1. 总结:
    diff算法的策略是深度优先,同层比较;
    两个节点之间比较会根据是否拥有子节点或文本节点采取不同的操作;
    两组子节点之间的比较采用首尾两两比较四次,如果不匹配则进行普通便利,最后批量处理收尾;
    借助key可以快速地查找相同节点,因此patch非常高效。
    

7.Q:组件化的理解

A:

  • 组件是独立、可复用的组织单元。组件化是vue的核心特性之一。开发者可以使用小型、独立、可复用组件开发大型应用。
  • 优势:组件化可以提高复用性、维护性、测试性以及开发的效率等
  • 组件化技术:属性prop,自定义事件和插槽,主要用于组件通信和扩展等
  • vue组件的特点:vue的组件是基于配置的。日常开发的单页面组件实际上是组件配置而非组件的构造函数,它经过webpack调用vue-loader后编译为render函数,导出配置对象,经框架extend方法转化为组件构造函数,基于VueComponent,扩展自Vue 。
  • 分类:
    • 页面组件:一些页面组件,通用性低,但常用。
    • 业务组件:涉及具体业务,如登录、注册。
    • 通用组件:如按钮、输入框、提示框。
  • 合理拆分组件粒度,可以大幅提高组件的效率。
    • 注意:组件是自上而下实例化,但是加载是自下而上的。
  • 日常开发中,组件是单一、独立、功能单元,是分治思想的落地,因此组件应该高内聚、低耦合的。
  • 遵守单向数据流的原则。

8.Q:vue的设计原则的理解

A:

  • 渐进式框架
    • 自底向上式设计,核心库只关注视图,便于与第三方库或者先有项目结合。也可与现代工具链或者类库结合,以支持复杂的应用。
  • 易用性
    • 上手要求低,数据响应式、声明式模板语法和基于配置的组件系统等核心特性,使得开发者至于要注重于业务。
  • 灵活性
    • 核心库(声明式渲染和组件系统)可以完成基础的开发需求,根据需求可以自由结合各种技术框架。
  • 高效性
    • 虚拟dom和高效的diff语法。(3.0版本引用proxy优化了数据响应式,优化了静态内容编译,更加高效。)

9.Q:浅谈一下mvc、mvp、mvvm的理解

A: 这三种框架模式,都是为了解决model层与view层的耦合问题。

  • mvc模式是最早出现的,主要应用于后端,优点是封层清晰,缺点是数据流混乱、维护困难。早期的前端会使用此模式的view层。理论上数据流向是单向的view-controller-model-view形成一个闭合环,controller监听调用从而修改model。但是实际业务中,model和view层的数据是双向的,同时相当部分的逻辑写在view层,这使得随着项目的壮大,理清model的数据来源变得愈加困难,同时controller显得更加的鸡肋。前端有backbone.js。
  • mvp的出现是为了解决model和view层的直接接触导致数据流向混乱的问题,presenter层相当于中间人,监管者数据在两层之间的流动通信。缺点在于随着项目壮大,p层愈发庞大,难以维护。前端很少有知名的mvp模式的框架,安卓等客户端开发者可能有使用到。
  • mvvm模式,则利用view-model作为胶水层,解决了耦合性的问题,保证数据单向流动的同时也可结合组件化开发的思想保障可维护性。同时可以实现以数据驱动,开发者仅仅关注业务逻辑,由框架来完成dom操作的高效更新和维护。

10.Q:vue性能优化的方法?(代码层面)

A:

  • 路由懒加载。
  • 组件缓存,keep-alive使用。
  • 插件或组件按需加载,避免全量加载。(一般是基于Babel-plugin的。)
  • 频繁切换显示隐藏的组件使用v-show替代v-if。
  • v-for与v-if禁止在同一组件上使用。
  • 长列表性能优化
    • 对于那些无需变化的数据进行固定,减少响应式带来的消耗。譬如,对数据使用Object.freeze()。
    • 虚拟滚动,只渲染少部分数据。
  • 事件及时销毁。通常来说,组件在销毁时会自动一并销毁内部事件。但是对于用户自己添加的类似定时器这种事件,应该在beforeDestory钩子中手动销毁,避免出现内存泄漏的情况。
  • 图片懒加载。
  • 组件分割。组件尽可能的小而美,每个组件执行单一功能。对于大计算量的组件可以提取为一个独立组件。
  • 对于纯展示组件,可以采用函数式组件。
    <template functional></template>
    
  • 数据本地化。在计算属性中使用某些数据时,可以将数据存储在变量中,避免频繁的通过this进行调用。
  • ssr。

11.Q:vue3新特性的理解

因本人并没有vue3的实际生产项目经验,所以仅供参考。

  • 更快
    • 虚拟dom的重写
    • 基于proxy实现的数据响应式
    • 静态节点提升
    • 静态属性提升
    • 优化slots生成
  • 更小
    • 打包时,通过摇树(tree-shrinking)操作去除无用的冗余代码,从而减小打包输出文件的体积。
  • 更容易维护
    • typescript + 模块化架构
  • 开发更友好
    • 核心的编译器和运行时脱离平台,可以轻松的在各种平台上运行。
  • 更易使用
    • typescript可以静态校验,提前感知大部分错误。
    • 更好的调试体验。
    • 独立的响应式模块
    • composition API

12.Q:vuex的使用及其理解

A:

  • 定义:它是一个vue专用的状态集中管理模式,单例模式,以一种可预测的方式集中管理全局状态。
  • 解决了什么问题:解决了全局状态共享、管理不便的问题。虽然通过vue的通信方式可以实现数据的共享,但是对于大型项目多处使用到共享的数据时,修改起来很是繁琐且不可预测修改方与修改位置。
  • 使用场景:当一个项目足够大或者复杂的时候,需要管理的全局状态增多,自然就会用到vuex。
  • 使用方式:
    • 首先根实例中引入vuex的单实例。可通过实例的$store访问state中集中管理的各种全局状态。
    • 需要修改状态时,可以通过commit发起请求修改,而且只能通过commit进行修改。
    • 如果涉及到异步或者复杂的逻辑,可以使用dispatch一个action来访问actions,执行完相关逻辑或异步后如果有状态需要修改则需要调用对应的commit。
    • 对于复杂的项目需要进行模块化管理,使用modules拆分模块,多个子模块需要设置namespace,使用派发dispatch和提交commit时需要添加命名前缀。
  • 简述原理:
    • 做单向数据流需要维护了一个响应式的类,通过把state中存储的状态放进一个vue实例data选项中进行实例化,这样就可以做到了全局响应式渲染。
    • 对于可预测性,vuex提供了dispatch和commit供用户来调用actions和mutations,这就给vuex机会再执行用户给定的action和mutation之前做事的机会,确保了用户操作的规范。

13.Q:vue组件的通信方式

A:

按层级分类描述。下方通信方式描述存在重叠,是因为诸如跨层级组件通信也可以完成父子、兄弟组件之间的通信目的。

  • 父子组件之间
    • props、$emit/$on。通过props自父组件向子组件传值,通过emit/emit/on子组件通过事件方式向父组件发起唤醒。
    • eventBus 。定义$bus,事件总成。
    • provide/inject。父组件通过provide对外提供消息,子组件通过inject注入所需的消息。
    • $root .通过相同的根结点。
    • ref。通过引用组件实例或着dom元素实例,发送消息。
    • $children / $parent。父子组件之间通过引用进行通信。
    • $attrs/$listener。
    • vuex
  • 兄弟组件之间/跨层级组件之间
    • $children/$parent
    • provide/inject
    • $root
    • eventBus
    • vuex