vue2知识总结

Vue基础知识
1. Vue的最大优势是什么

​ 轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十kb;

​ 简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;双向数据绑定:保留了angular的特点,在数据操作方面更为简单;

​ 组件化:保留了react的优点,实现了html的封装和重用,在构建单页面应用方面有着独特的优势;

​ 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;

​ 虚拟DOM:dom操作是非常耗费性能的, 不再使用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom不过是换了另一种方式;

​ 运行速度更快:相比较与react而言,同样是操作虚拟dom,就性能而言,vue存在很大的优势

2. Vue和jQuery区别是什么

​ jQuery应该算是一个插件, 里面封装了各种易用的方法, 方便你使用更少的代码来操作dom标签

​ Vue是一套框架, 有自己的规则和体系与语法, 特别是设计思想MVVM, 让数据和视频关联绑定, 省略了很多DOM操作. 然后指令还给标签注入了更多的功能

3. mvvm和mvc区别是什么

​ MVC: 也是一种设计模式, 组织代码的结构, 是model数据模型, view视图, Controller控制器, 在控制器这层里编写js代码, 来控制数据和视图关联

​ MVVM: 即Model-View-ViewModel的简写。即模型-视图-视图模型, VM是这个设计模式的核心, 连接v和m的桥梁, 内部会监听DOM事件, 监听数据对象变化来影响对方. 我们称之为数据绑定, 在MVVM框架中,将View层的状态和行为抽象化,视图 UI 和业务逻辑分开,ViewModel层通过双向绑定,把View层和Model层联系起来,从而使View层和Model层能够自动同步, Model:数据模型层,用来处理业务逻辑和与数据库交互; View:视图层,DOM ViewModel:视图模型层,用来处理Model层和View层的交互

​ VM的实现原理:

  • 响应式:Object.defineProperty()来做数据劫持和响应式
  • 模板解析:Vue中的render函数,来将模版转换成虚拟DOM
  • 将虚拟DOM渲染成html:通过updateComponent方法实现
4. Vue常用修饰符有哪些

表单修饰符:

  • lazy(当光标离开标签时,才会将值赋值给value)
  • trim(过滤掉两边的空格)
  • number(自动将用户的输入值转为数值类型)

事件修饰符:

  • stop ;阻止事件的冒泡,相当于调用了event.preventPropagation方法
  • prevent ;阻止了事件的默认行为,相当于调用了event.preventDefault方法,v-on:click.self.prevent 只会阻止对元素自身的点击
  • self ;只当在 event.target 是当前元素自身时触发处理函数 用 v-on:click. prevent.self 会阻止所有的点击,而
  • once ;绑定了事件以后只能触发一次,第二次就不会触发
  • capture;事件捕获,从顶层往下触发
  • passive;用于提升移动端scroll事件的性能
  • native ;如果在自定义组件标签上绑定原生事件,则需要加上.native

v-bind修饰符:sync 实现子组件props的双向绑定

5. Vue2.x兼容IE哪个版本以上

​ 不支持ie8及以下,部分兼容ie9 ,完全兼容10以上, 因为vue的响应式原理是基于es5的Object.defineProperty(),而这个方法不支持ie8及以下。

6 . 对Vue渐进式的理解

​ 渐进式代表的含义是:主张最少, 自底向上, 增量开发, 组件集合, 便于复用

7 . v-model 数据双向绑定原理

vue中的v-model无非就是在单向数据绑定的基础上给对应表单元素(input、select等)添加了(input、change)事件,来动态修改model和view,从而达到双向数据绑定的效果

8. vue原理

当创建 Vue 实例时,vue 会遍历 data 选项的属性,利用 Object.defineProperty 为属性添加 getter 和 setter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。

每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有 computed watcher,user watcher 实例),之后依赖项被改动时,setter 方法会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染

vue.js 采用数据劫持结合发布-订阅模式,通过 Object.defineproperty 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调

9. v-show和v-if的区别

​ v-show 和v-if都是true的时候显示,false的时候隐藏

​ v-show是采用的display:none

​ v-if采用惰性加载

​ 如果需要频繁切换显示隐藏需要使用v-show

10. 说出至少4个Vue指令及作用

​ v-for 根据数组的个数, 循环数组元素的同时还生成所在的标签

​ v-show 显示内容

​ v-if 显示与隐藏

​ v-else 必须和v-if连用 不能单独使用 否则报错

​ v-bind 动态绑定 作用: 及时对页面的数据进行更改, 可以简写成:分号

​ v-on 给标签绑定函数,可以缩写为@,例如绑定一个点击函数 函数必须写在methods里面

​ v-text 解析文本

​ v-html 解析html标签

11. 为什么避免v-for和v-if在一起使用

​ Vue 处理指令时,v-for 比 v-if 具有更高的优先级, 虽然用起来也没报错好使, 但是性能不高, 如果你有5个元素被v-for循环, v-if也会分别执行5次.

12. Vue 中怎么自定义过滤器

​ Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和v-bind表达式

​ 全局的用Vue.filter() 局部的用filters属性

13. Vue中:key作用, 为什么不能用索引

​ :key是给v-for循环生成标签颁发唯一标识的, 用于性能的优化

​ 因为v-for数据项的顺序改变,Vue 也不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素

​ :key如果是索引, 因为索引是连续的, 如果删除其中某一个, 会导致最后一个被删除

​ 当我们再删除的时候, :key再根据数据来把新旧的dom对比时, 删除:key不存在的对应的标签(添加也是一样的插入到指定位置, 别的都不会动)

14. 数组更新有的时候v-for不渲染

​ 因为vue内部只能监测到数组顺序/位置的改变/数量的改变, 但是值被重新赋予监测不到变更, 可以用 Vue.set() / vm.$set()

15. 请说下封装 vue 组件的过程

​ 首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模块,解决了我们传统项目开发:效率低、难维护、复用性等问题。

  • 分析需求:确定业务需求,把页面中可以复用的结构,样式以及功能,单独抽离成一个组件,实现复用
  • 具体步骤:Vue.component 或者在new Vue配置项components中, 定义组件名, 可以在props中接受给组件传的参数和值,子组件修改好数据后,想把数据传递给父组件。可以采用$emit方法。
16. 跨组件通信的方法有哪些
  • 父——>子 props/ e m i t ,父组件通过 ‘ p r o p s ‘ 的方式向子组件传递数据,子组件通过 ‘ emit,父组件通过`props`的方式向子组件传递数据,子组件通过` emit,父组件通过props的方式向子组件传递数据,子组件通过emit(绑定事件)` 可以向父组件通信
  • 子组件向父组件传递数据 ,父: @自定义事件名=“父methods函数” 子: this.$emit(“自定义事件名”, 传值)
  • provide/inject 简单来说就是父组件中通过provide来提供变量, provide:{for:‘demo’} 然后再子组件中通过inject来注入变量 ,inject:{‘for’} 子组件中使用:{{demo}}
  • ref/refs ,**ref:**如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据
  • eventBus 事件中心管理兄弟组件间的通信
  • localStorage / sessinStorage,
window.localStorage.setItem(key,value)//保存数据
window.localStorage.getItem(key) //获取数据
window.localStorage.removeItem(key)//删除数据
  • 注意用 JSON.parse() / JSON.stringify() 做数据格式转换 localStorage / sessionStorage可以结合 vuex, 实现数据的持久保存,同时使用vuex解决数据和状态混乱问题

  • vuex 进行传值

17. Vue 组件 data 为什么必须是函数

每个组件都是 Vue 的实例, 为了独立作用域, 不让变量污染别人的变量

18、Vue 的 nextTick 的原理

$nexttick的作用:

​ Vue在更新DOM的时候是异步的,只要数据发生变化,Vue就会开启一个异步更新队列,$nextTick就是等队列中所有的数据变化完成之后,再统一进行更新。

​ 1. 为什么需要 nextTick ,Vue 是异步修改 DOM 的并且不鼓励开发者直接接触 DOM,但有时候业务需要必须对数据更改–刷新后的 DOM 做相应的处理,这时候就可以使用 Vue.nextTick(callback)这个 api 了。

​ 2. 理解原理前的准备 首先需要知道事件循环中宏任务和微任务这两个概念,常见的宏任务有 script, setTimeout, setInterval, setImmediate, I/O, UI rendering 常见的微任务有 process.nextTick(Nodejs),Promise.then(), MutationObserver;

  1. 理解 nextTick 的原理正是 vue 通过异步队列控制 DOM 更新和 nextTick 回调函数先后执行的方式。如果大家看过这部分的源码,会发现其中做了很多 isNative()的判断,因为这里还存在兼容性优雅降级的问题
19、vue生命周期总共分为几个阶段

Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。

  • beforeCreate: 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用

  • created: 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer), 属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见

  • beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用

  • mounted: el 被新创建的 vm. e l 替换,并挂载到实例上去之后调用该钩子。如果 r o o t 实例挂载了一个文档内元素,当 m o u n t e d 被调用时 v m . el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm. el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.el 也在文档内

  • beforeUpdate: 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行

  • updated: 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子

  • activated: keep-alive 组件激活时调用。该钩子在服务器端渲染期间不被调用

  • deactivated: keep-alive 组件停用时调用。该钩子在服务器端渲染期间不被调用

  • beforeDestroy: 实例销毁之前调用,实例仍然完全可用,该钩子在服务器端渲染期间不被调用。

  • destroyed: Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用

  • errorCaptured(2.5.0+ 新增:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播

20、第一次加载页面会触发哪几个钩子函数

当页面第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子函数

21. 路由缓存

使用Keep-alive标签包裹router-view就可以实现全部缓存

<keep-alive>
  <router-view> </router-view>
</keep-alive>

缓存单个指定的路由,同样使用Keep-alive标签包裹router-view,在Keep-alive中使用include指定需要缓存的页面的名称即可

<keep-alive include='缓存页面的名称'>
  <router-view> </router-view>
</keep-alive>
  • 缓存多个指定路由

需要使用两个router-view两个标签,一个作为缓存的出口一个作为不换缓存的出口,然后在路由配置的时候给缓存的页面加上meta属性,然后设置keepAlive的值

<keep-alive>
  <router-view v-if="$route.meta.keepAlive"> </router-view>
</keep-alive>
 <router-view v-if="!$route.meta.keepAlive"> </router-view>
 {
   path:'/car'
   name:'car'
   component:Car,
   meta:{keepAlive:true} // true缓存 false不缓存
 }
22. 跟keep-alive有关的生命周期是哪些

​ **1****在开发Vue项目的时候,大部分组件是没必要多次渲染的,所以Vue提供了一个内置组件keep-alive来缓存组件内部状态,避免重新渲染

2)生命周期函数:**在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子:activated 与 deactivated。

1**、activated钩子:**在在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用。

2**、Activated钩子调用时机:** 第一次进入缓存路由/组件,在mounted后面,beforeRouteEnter守卫传给 next 的回调函数之前调用,并且给因为组件被缓存了,再次进入缓存路由、组件时,不会触发这些钩子函数,beforeCreate created beforeMount mounted 都不会触发

1**、deactivated钩子:**组件被停用(离开路由)时调用。

2**、deactivated钩子调用时机**:使用keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了,这个钩子可以看作beforeDestroy的替代,如果你缓存了组件,要在组件销毁的的时候做一些事情,可以放在这个钩子里,组件内的离开当前路由钩子beforeRouteLeave => 路由前置守卫 beforeEach =>全局后置钩子afterEach => deactivated 离开缓存组件 => activated 进入缓存组件(如果你进入的也是缓存路由)

23. is特性

 1)动态组件**

  <component :is="componentName"></component>

componentName可以是在本页面已经注册的局部组件名和全局组件名,也可以是一个组件的选项对象。 当控制componentName改变时就可以动态切换选择组件

 2)is的用法**

​ 有些HTML元素,诸如 ul、ol、table和 select,对于哪些元素可以出现在其内部是有严格限制的

​ 而有些HTML元素,诸如 li、tr 和 option,只能出现在其它某些特定的元素内部

   <ul>
      <card-list></card-list>
    </ul>

​ 所以上面 card-list 会被作为无效的内容提升到外部,并导致最终渲染结果出错。应该这么写

 <ul>
     <li is="cardList"></li>
  </ul>
24.在vue中踩过的坑

​ 1、第一个是给对象添加属性的时候,直接通过给data里面的对象添加属性然后赋值,新添加的属性不是响应式的

​ 【解决办法】通过Vue.set(对象,属性,值)这种方式就可以达到,对象新添加的属性是响应式的

2、 在created操作dom的时候,是报错的,获取不到dom,这个时候实例vue实例没有挂载

​ 【解决办法】通过:Vue.nextTick(回调函数进行获取)

25.做过哪些Vue的性能优化

(1)编码阶段
尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用
如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件
在更多的情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载
(2)SEO优化
预渲染
服务端渲染SSR
(3)打包优化
压缩代码
Tree Shaking/Scope Hoisting
使用cdn加载第三方模块
多线程打包happypack
splitChunks抽离公共文件
sourceMap优化

26、vue2和vue3的区别
  • vue2和vue3双向数据绑定原理发生了改变,vue2的双向数据绑定是利用了es5 的一个Object.definepropert() 对数据进行劫持来实现的,vue3中使用了es6的proxyAPI对数据进行处理,相比与vue2,使用proxyAPI 优势有:defineProperty只能监听某个属性,不能对全对象进行监听;可以省去for in 、闭包等内容来提升效率;可以监听数组,不用再去单独的对数组做特异性操作,vue3可以检测到数组内部数据的变化

  • Vue3支持碎片

  • Vue2 与vue3 最大的区别是vue2使用选项类型api,对比vue3合成型api

  • 建立数据data vue2是把数据放在data中 vue3就需要使用一个新的setup()方法 此方法在组建初始化构造的时候触发

  • 父子传参不同,setup()函数特性,

    1.setup()函数接收两个参数:props、context(包含attrs、slots、emit)
    2.setup函数是处于生命周期beforeCreated和created俩个钩子函数之前
    3.执行setup时,组件实例尚未被创建(在setup()内部,this不会是该活跃实例得引用,即不指向vue实例, Vue为了避免我们错误得使用,直接将setup函数中得this修改成了undefined)
    4.与模板一起使用时,需要返回一个对象
    5.因为setup函数中,props是响应式得,当传入新的prop时,它将会被更新,所以不能使用es6解构,因为它 会消除prop得响应性,如需解构prop,可以通过使用setup函数中得toRefs来完成此操作。
    6.父传子,用props,子传父用事件 Emitting Events。在vue2中,会调用this$emit然后传入事件名和对象;在vue3中得setup()中得第二个参数content对象中就有emit,那么我们只要在setup()接收第二个参数中使用分解对象法取出emit就可以在setup方法中随意使用了。
    7.在setup()内使用响应式数据时,需要通过 .value 获取

    8.从setup() 中返回得对象上得property 返回并可以在模板中被访问时,它将自动展开为内部值。不需要在模板中追加.value。

    1. setup函数只能是同步的不能是异步的。
27.vue为什么是响应式

因为它可以对数据所做的更改做出反应。vue响应式也叫作数据双向绑定,通过Object.defineProperty()方法把数据(data)设置为getter和setter的访问形式,这样我们就可以在数据被修改时在setter方法设置监视修改页面信息,也就是说每当数据被修改,就会触发对应的set方法,然后我们可以在set方法中去调用操作dom的方法。如果页面有input用v-model绑定数据,我们需要在这种绑定了data的input元素上添加监听,添加input事件监听,每当input事件被触发时,就修改对应的data。

  • 核心点: Object.defineProperty
  • 默认 Vue 在初始化数据时,会给 data 中的属性使用 Object.defineProperty 重新定义所有属
    性,当页面取到对应属性时。会进行依赖收集(收集当前组件的watcher) 如果属性发生变化会通
    知相关依赖进行更新操作
28. sync修饰符怎么用**

实现子组件props的双向绑定,修饰符可以实现子组件与父组件的双向绑定,并且可以实现子组件同步修改父组件的值 ,,sync实际上就是一个语法糖

sync的用法

<组件 :属性名.sync="变量"   />

其实他本质是一个语法糖:

<组件 :属性名="变量" @update:属性名 = "变量=$event"   />

其实是v-model的另外一种使用展示,这也是为什么我们推荐以 update:myPropName 的模式触发事件取而代之。举个例子,在一个包含 title prop 的假设的组件中,我们可以用以下方法表达对其赋新值的意图:

this.$emit('update:title', newTitle)

然后父组件可以监听那个事件并根据需要更新一个本地的数据 property。例如:

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

为了方便起见,我们为这种模式提供一个缩写,即 .sync 修饰符:

<text-document v-bind:title.sync="doc.title"></text-document>

注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用

29.vue中怎么增加和删除dom**

vue的思想是通过数据操作dom,所以我们根据data中的数据进行对dom的遍历 从而操作数据就可以对vue进行一个动态的添加或者删除

30.侦听器和计算属性有什么区别

计算属性(computed):

  • 计算属性支持缓存,只有当依赖项数据发生改变,才会重新进行计算

  • 计算属性是不支持异步操作的,当计算属性computed内有异步操作的时候,无法监听数据的变化,此时计>算属性computed是无效的

  • 如果计算属性computed需要对数据进行修改,需要写get和set两个方法,当数据变化时,就调用set方法

  • 计算属性computed擅长处理的场景,就是一个数据受多个数据的影响;比如项目中的购物车计算价格,数据的全选和反选

侦听器(watch):

  • 侦听器是不支持缓存的,数据变化,就会直接触发响应的操作
  • watch支持异步的操作:监听的函数接受到两个参数,第一个参数是最新的值,第二个参数是输入之前的值
  • 侦听器中的immediate属性: immediate设置为 true 的意思就是组件加载立即触发回调函数执行
  • 侦听器中的deep属性,deep设置为 true 的意思就是深入侦听,修改对象里面任何一个属性都会触发这个监听器里面的 handler 方法来处理响应的逻辑
  • 侦听器watch擅长处理的业务场景:一个数据影响多个数据,比如项目中的搜索框
31.计算属性里面的值可以直接修改吗?如果一定要修改如何实现

计算属性不能改的原因是 :不能直接修改计算属性里面的值计算属性默认没有实现setter方法,只实现了 getter方法,
所以我们只能取值,不能赋值,如果想要修改计算属性 那就自己实现setter方法

32computed数据改变了却没有更新到视图中*

他会对属性进行缓存,只有在外部的属性值改变时才会发生改变,需要另外写一个set函数

33omputed 的实现原理【原理题】

computed 本质是一个惰性求值的观察者, 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例,其内部通过 this.dirty 属性标记计算属性是否需要重新求值,当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher, computed watcher 通过 this.dep.subs.length 判断有没有订阅者,有的话重新计算对比新旧值,变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化,没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)特性。)

34插槽和插槽的优势
  • 默认插槽外

在父组件App.Vue中引入子组件,并在引入的子组件标签内插入需要的html元素,在子组件中把需要用插槽的地方用<slot>标签替代:

<template>
  <div id="app">
    <!-- 默认插值 -->
    <StudyM title="游戏列表">
      <ul>
        <li v-for="game , index in games" :key="index">{{game}}</li>
      </ul>
    </StudyM>
    <StudyM title="推荐音乐">
      <ul>
        <li v-for="music , index in musics" :key="index">{{music}}</li>
      </ul>
    </StudyM>
    </StudyM>
  </div>
</template>

<script>
  //引入组件
  import StudyM from './components/StudyM.vue'
export default {
  name: 'App',
  components: {
    StudyM
  },
  data(){
    return{
      games:['英雄联盟', '炉石传说', '穿越火线', 'QQ飞车', '吃鸡'],
      musics:['迷途羔羊', '红玫瑰', '富士山下', '麻雀', '妈妈的话', '故乡'],
    }
  }
}
</script>
StudyM.vue:
<template>
  <div class="box">
    <h4>{{title}}</h4>
    <slot></slot>
  </div>
</template>
<script>
export default {
    name:'StudyM',
    props:['title'],
}
</script>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNaSsEzg-1684320636724)(E:\学习\md笔记\images\6ebeb8dbf53e45e79cac7b9001a8f5b3.png)]

  • 具名插槽

遇到这种场景,在一个组件中,我们虽然可以复用组件的结构,但是往往,有时候我们会遇到一个组件多用,但是结构稍微有所差别的情况,这时,就会用得到具名插值

<template>
  <div id="app">
    <!-- 默认插值 -->
    <StudyM title="游戏列表">
      <!-- 具名插槽的写法一 -->
      <template slot="one">
        <ul>
          <li v-for="game , index in games" :key="index">{{game}}</li>
        </ul>
      </template>
      <!-- 具名插槽的写法二 -->
      <template v-slot:tow>
        <a href="https://lol.qq.com/main.shtml">英雄联盟官网由此进入</a><br/><br>
      </template>
    </StudyM>
    <StudyM title="推荐音乐">
      <template slot="one">
        <ul>
          <li v-for="music , index in musics" :key="index">{{music}}</li>
        </ul>
      </template>
      <template slot="tow">
        <button>点击进入QQ音乐</button>&nbsp;&nbsp;&nbsp;
        <button>点击进入网易云音乐</button>
      </template>
    </StudyM>
    <StudyM title="电影推荐">
      <template slot="one">
        <ul>
          <li v-for="movie , index in movies" :key="index">{{movie}}</li>
        </ul>
      </template>
      <template slot="tow">
        <video controls src="https://www.oppo.com/content/dam/oppo/product-asset-library/find/find-n/v1/assets/tvc-preview-3d0357.mp4"></video>
      </template>
    </StudyM>
  </div>
</template>
<script>
  //引入组件
  import StudyM from './components/StudyM.vue'
export default {
  name: 'App',
  components: {
    StudyM
  },
  data(){
    return{
      games:['英雄联盟', '炉石传说', '穿越火线', 'QQ飞车', '吃鸡'],
      musics:['迷途羔羊', '红玫瑰', '富士山下', '麻雀', '妈妈的话', '故乡'],
      movies:['蜘蛛侠', '复仇者联盟', '金刚狼', '触不可及', '唐人街探案','当幸福来敲门']
    }
  }
}
</script>
StudyM.vue
<template>
  <div class="box">
    <h4>{{title}}</h4>
    <slot name="one"></slot>
    <slot name="tow"></slot>
  </div>
</template>
<script>
export default {
    name:'StudyM',
    props:['title'],
}
</script>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmwx9hUq-1684320636725)(E:\学习\md笔记\images\9bc2df195dc244ab827f182090d9cfdb.png)]

  • 作用域插槽

数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定,也就是说,作用域插槽的不同之处就在于,数据不在父组件身上,而是在子组件身上,且组件的结构和内容由父组件决定。作用域组件限定了组件内结构和数据的展示范围,以便在开发中我们可以根据一个组件而不断变换其中的内容和结构

<template>
  <div id="app">
    <!-- 默认插值 -->
    <StudyM title="游戏列表">
      <template scope="one">
        <!-- 第一种结构 -->
        <ul>
          <li v-for="m,index in one.movies" :key="index">{{m}}</li>
        </ul>
      </template>
    </StudyM>

    <StudyM title="游戏列表">
      <template scope="one">
        <!-- 第二种结构 -->
        <ol>
          <li v-for="m,index in one.movies" :key="index">{{m}}</li>
        </ol>
      </template>
    </StudyM>
    <StudyM title="游戏列表">
      <template scope="one">
        <!-- 第三种结构 -->
          <h4 v-for="m,index in one.movies" :key="index">{{m}}</h4>
      </template>
    </StudyM>
  </div>
</template>
<script>
  //引入组件
  import StudyM from './components/StudyM.vue'
export default {
  name: 'App',
  components: {
    StudyM
  },
}
</script>
StudyM.vue
<template>
  <div class="box">
    <h4>{{title}}</h4>
    <!-- 把数据传给插入的html部分 -->
    <slot :movies="movies"></slot>
  </div>
</template>
<script>
export default {
    name:'StudyM',
    props:['title'],
    data(){
      return{
        movies:['蜘蛛侠', '复仇者联盟', '金刚狼', '触不可及', '唐人街探案','当幸福来敲门']
      }
    }
}
</script>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CJM3uWyC-1684320636725)(E:\学习\md笔记\images\bfc3780fa4d2474ba05fc304503fba8e.png)]

35、vue中 key 值的作用

当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key的作用主要是为了高效的更新虚拟DOM 【防止采用就地复用策略进行改变】,

**diff 算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的 key 与旧节点进行比对,从而找到相应旧节点.**更准确 : 因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确,如果不加 key,会导致之前节点的状态被保留下来,会产生一系列的 bug

更快速 : key 的唯一性可以被 Map 数据结构充分利用,相比于遍历查找的时间复杂度 O(n),Map 的时间复杂度仅仅为 O(1),源码如下:

function createKeyToOldIdx(children, beginIdx, endIdx) {
 let i, key;
 const map = {};
 for (i = beginIdx; i <= endIdx; ++i) {
  key = children[i].key;
  if (isDef(key)) map[key] = i;
 }
 return map;
}
36、vue 是如何对数组方法进行变异的 ?【原理题】

我们先来看看源码

const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);
const methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse"
];
/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function(method) {
  // cache original method
  const original = arrayProto[method];
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args);
    const ob = this.__ob__;
    let inserted;
    switch (method) {
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
        break;
    }
    if (inserted) ob.observeArray(inserted);
    // notify change
    ob.dep.notify();
    return result;
  });
});
/**
 * Observe a list of Array items.
 */
Observer.prototype.observeArray = function observeArray(items) {
  for (var i = 0, l = items.length; i < l; i++) {
    observe(items[i]);
  }
};

简单来说,Vue 通过原型拦截的方式重写了数组的 7 个方法,首先获取到这个数组的ob,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 对新的值进行监听,然后手动调用 notify,通知 render watcher,执行 update

37、聊聊 keep-alive 的实现原理和缓存策略 【原理】

<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。

当组件在 <keep-alive> 内被切换,它的 activateddeactivated 这两个生命周期钩子函数将会被对应执行。

export default {
  name: "keep-alive",
  abstract: true, // 抽象组件属性 ,它在组件实例建立父子关系的时候会被忽略,发生在 initLifecycle 的过程中
  props: {
    include: patternTypes, // 被缓存组件
    exclude: patternTypes, // 不被缓存组件
    max: [String, Number] // 指定缓存大小
  }, 
  created() {
    this.cache = Object.create(null); // 缓存
    this.keys = []; // 缓存的VNode的键
  }, 
  destroyed() {
    for (const key in this.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() {
    // 获取第一个子元素的 vnode
    const slot = this.$slots.default;
    const vnode: VNode = getFirstComponentChild(slot);
    const componentOptions: ?VNodeComponentOptions =
      vnode && vnode.componentOptions;
    if (componentOptions) {
      // name不在inlcude中或者在exlude中 直接返回vnode
      // check pattern
      const name: ?string = getComponentName(componentOptions);
      const { include, exclude } = this;
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode;
      } 
      const { cache, keys } = this;
      // 获取键,优先获取组件的name字段,否则是组件的tag
      const key: ?string =
        vnode.key == null
          ? // same constructor may get registered as different local components
            // so cid alone is not enough (#3269)
            componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : "")
          : vnode.key;
      // 命中缓存,直接从缓存拿vnode 的组件实例,并且重新调整了 key 的顺序放在了最后一个
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        remove(keys, key);
        keys.push(key);
      }
      // 不命中缓存,把 vnode 设置进缓存
      else {
        cache[key] = vnode;
        keys.push(key);
        // prune oldest entry
        // 如果配置了 max 并且缓存的长度超过了 this.max,还要从缓存中删除第一个
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }
      // keepAlive标记位
      vnode.data.keepAlive = true;
    }
    return vnode || (slot && slot[0]);
  }
};

原理

  1. 获取 keep-alive 包裹着的第一个子组件对象及其组件名
    根据设定的 include/exclude(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例
  2. 根据组件 ID 和 tag 生成缓存 Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该 key 在 this.keys 中的位置(更新 key 的位置是实现 LRU 置换策略的关键)
  3. 在 this.cache 对象中存储该组件实例并保存 key 值,之后检查缓存的实例数量是否超过 max 的设置值,超过则根据 LRU 置换策略删除最近最久未使用的实例(即是下标为 0 的那个 key)
  4. 最后组件实例的 keepAlive 属性设置为 true,这个在渲染和执行被包裹组件的钩子函数会用到,

LRU 缓存淘汰算法

LRU(Least recently used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

keep-alive 的实现正是用到了 LRU 策略,将最近访问的组件 push 到 this.keys 最后面,this.keys[0]也就是最久没被访问的组件,当缓存实例超过 max 设置值,删除 this.keys[0]

38、vm.$set()实现原理是什么? 【原理题】

受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。

由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。

那么 Vue 内部是如何解决对象新增属性不能响应的问题的呢

export function set(target: Array | Object, key: any, val: any): any {
  // target 为数组
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 修改数组的长度, 避免索引>数组长度导致splice()执行有误
    target.length = Math.max(target.length, key);
    // 利用数组的splice变异方法触发响应式
    target.splice(key, 1, val);
    return val;
  }
  // target为对象, key在target或者target.prototype上 且必须不能在 Object.prototype 上,直接赋值
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val;
  }
  // 以上都不成立, 即开始给target创建一个全新的属性
  // 获取Observer实例
  const ob = (target: any).__ob__;
  // target 本身就不是响应式数据, 直接赋值
  if (!ob) {
    target[key] = val;
    return val;
  }
  // 进行响应式处理
  defineReactive(ob.value, key, val);
  ob.dep.notify();
  return val;
}

如果目标是数组,使用 vue 实现的变异方法 splice 实现响应式
如果目标是对象,判断属性存在,即为响应式,直接赋值
如果 target 本身就不是响应式,直接赋值
如果属性不是响应式,则调用 defineReactive 方法进行响应式处理

39、vue里面如何操作样式
  • 操作style
    • 对象语法 :style="{CSS属性名:变量}"
    • 数组语法 :style="[样式描述变量1,样式描述变量2]"
    • 直接字符串 style='css属性名:css属性值;css属性名2:css属性值2;...'
  • 操作class
    • 对象语法 :style="{class名:布尔变量}"
    • 数组语法 :class="[变量1,变量2,...]"
    • 三木语法: :class = "条件 ? 'class名1':'class名2'"
40、vue如何强制刷新组件

1.使用this.$forceUpdate强制重新渲染

如果要在组件内部中进行强制刷新,则可以调用**this.$forceUpdate()**强制重新渲染组件,从而达到更新目的。

<template>
<button @click="reload()">刷新当前组件</button>
</template>
<script>
export default {
    name: 'comp',
    methods: {
        reload() {
            this.$forceUpdate()
        }
    }
}
</script>

2.使用v-if指令

如果是刷新某个子组件,则可以通过v-if指令实现。我们知道,当v-if的值发生变化时,组件都会被重新渲染一遍。因此,利用v-if指令的特性,可以达到强制刷新组件的目的。

<template>
    <comp v-if="update"></comp>
    <button @click="reload()">刷新comp组件</button>
</template>
<script>
import comp from '@/views/comp.vue'
export default {
    name: 'parentComp',
    data() {
        return {
            update: true
        }
    },
    methods: {
        reload() {
            // 移除组件
            this.update = false
            // 在组件移除后,重新渲染组件
            // this.$nextTick可实现在DOM 状态更新后,执行传入的方法。
            this.$nextTick(() => {
                this.update = true
            })
        }
    }
}
</script>
41、过滤器是什么?如何定义?如何使用?
  • 是对模板里面的数据进行某一类的操作,可以理解为模板的工具函数(如:直接在模板里面使用定义好的过滤器实现时间戳变格式化的时间;价格保留小数位等等处理)
// 全局注册
Vue.filter('过滤器名',(val,形参)=>{   // val表示传入的值, 第二个参数开始才是传入的实参 这个函数一定要有返回值
	// 处理
	return 结果
})
// 局部注册
  {
    filters:{
      过滤器名(val,形参){
        return 结果
      },
      过滤器名2(val,形参){
        return 结果
      },
      ...
    }
  }
- 如何使用: ```js
  {{ 变量 | 过滤器 }}   // 单个使用
  {{ 变量 | 过滤器1 | 过滤器2 | ... }}   // 串联使用,下一个过滤器函数接收的是上一个过滤器处理的结果
  {{ 变量 | 过滤器(实参) }}   // 过滤器传参
42、Vue.use 是什么? 如何使用?
  • vue.use 是vue的插件安装函数,调用use我们可以安装很多第三方的插件,如 vue-router,vuex, element-ui,vant等等,当然最重要的是可以安装我们自己开发的插件。

  • 开发插件

    Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:

    MyPlugin.install = function (Vue, options) {
      // 1. 添加全局方法或 property
      Vue.myGlobalMethod = function () {
        // 逻辑...
      }
      // 2. 添加全局资源
      Vue.directive('my-directive', {
        bind (el, binding, vnode, oldVnode) {
          // 逻辑...
        }
        ...
      })
      // 3. 注入组件选项
      Vue.mixin({
        created: function () {
          // 逻辑...
        }
        ...
      })
    
      // 4. 添加实例方法
      Vue.prototype.$myMethod = function (methodOptions) {
        // 逻辑...
      }
    }
    
    • 使用插件
    Vue.use(MyPlugin, { someOption: true })
    
    
43、对Vue.js的template编译的理解

简而言之,就是先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点)

44、说出 m o u n t 、 mount、 mountwatch、 d e l e t e 、 delete、 deleterefs、 s l o t s 、 slots、 slotsforceUpdate、 n e x t T i c k 、 nextTick、 nextTickdestroy这些API方法或属性的作用
  • $mount :用于挂载vue实例到某个节点上面,功能类似el选项, 有了它可以不配置el;
  • $watch:用于监听数据的变化,功能类似内部配置选项watch一样的效果。
  • d e l e t e :用于删除 d a t a 里面数据里面的对象的属性,可以实现响应式效果。功能和 delete:用于删除data里面数据里面的对象的属性,可以实现响应式效果。 功能和 delete:用于删除data里面数据里面的对象的属性,可以实现响应式效果。功能和set一致,只不过他是删除属性
  • $refs:用于获取原生DOM节点,或者某个组件对象
  • $slot: 用于获取当前组件传入的所有插槽内容
  • $forceUpdate: 用于刷新当前组件或vue实例,它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
  • $nextTick:将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。【简单理解即使等到DOM渲染完成再做应该的逻辑操作】
  • $destroy():只是完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。
45、EventBus注册在全局上时,路由切换时会重复触发事件,如何解决呢?

在有使用$on的组件中要在beforeDestroy钩子函数中用$off销毁。

46、Vue怎么改变插入模板的分隔符

delimiters选项,其默认是["{{", "}}"]

// 将分隔符变成ES6模板字符串的风格
new Vue({
  delimiters: ['${', '}']
})
47、Vue变量名如果以_、$开头的属性会发生什么问题?怎么访问到它们的值?

_ $ 开头的属性 不会 被 Vue 实例代理,因为它们可能和 Vue 内置的属性、API 方法冲突,你可以使用例如 vm.$data._property 的方式访问这些属性

48. 利用sync修饰符关闭新增弹层
// 子组件
this.$emit('changedialog', false) //触发事件
// 父组件
<child @changedialog="method" :showDialog="showDialog" />
// JS 里面
 method(value) {
    this.showDialog = value
}

vuejs为我们提供了**sync修饰符**,它提供了一种简写模式,也就是只要用sync修饰,就可以省略父组件的监听和方法,直接将值赋值给showDialog

// 子组件 update:固定写法 (update:props名称, 值)
this.$emit('update:showDialog', false) //触发事件
// 父组件 sync修饰符
<child  :showDialog.sync="showDialog" />
或者
<AddDept> @update:showDialog="showDialog = $event"</AddDept>

vue-router

49.路由之间是怎么跳转的?有哪些方式

1、

2、this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面

3、this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面

4、this.$router.go(n)向前或者后跳转n个页面,n可以是正数也可以是负数

50. r o u t e 和 route和 routerouter的区别

$route是路由信息对象,包括‘path 路径地址params 动态路由数据hash hash数据query search数据fullPath 完整地址matched 路由匹配 (可用于制作面包屑)name 路由名称等路由信息参数;
$router是路由实例对象,包括了路由的跳转方法,实例对象等

  • push/replace/go/back/forward
  • beforeEach/afterEach/…
  • addRoutes 方法,增加映射
51 、vue-router如何使用
// 【1、安装 】
npm i vue-router
// 【2、配置】
// src/router/index.js 创建配置文件
import Vue from 'vuex'
import Router  from "vue-router"
// 安装
Vue.use(Router)
// 定义路由映射
const routes = [
  {
    path:"/地址",component:()=>import('组件位置路径')
  }
]
// 创建路由实例
const router = new Router({
  routes,
})
// 暴露
export default router

// 【3、注入vue实例】
// main.js中操作
...
import router from './router'
...
new Vue({
  ...,
  router,  // 一旦注入,所有组件中都有$router和$route; $router表示路由对象实例; $route表示当前页面路由信息
  ...
})

// 【4、定义路由出口】
// App.vue中操作
<router-view></router-view>

// 【5、使用router-link跳转】
<router-link to="/地址"></router-link>
52. Vue的路由实现模式:hash模式和history模式

hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用 window.location.hash 读取。特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。

history模式:history采用HTML5的新特性;且提供了两个新方法: pushState(), replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更

53. Vue如何去除URL中的#

​ vue-router 默认使用 hash 模式,所以在路由加载的时候,项目中的 URL 会自带 “#”。如果不想使用 “#”, 可以使用 vue-router 的另一种模式 history:new Router ({ mode : ‘history’, routes: [ ]})

​ 需要注意的是,当我们启用 history 模式的时候,由于我们的项目是一个单页面应用,所以在路由跳转的时候,就会出现访问不到静态资源而出现 “404” 的情况,这时候就需要服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 “index.html” 页面。

54、何为命名路由
  • 其实就是给路由取一个名字,增加一个name属性
// 映射关系
const routes = [
	{ name:'名称' ,path:'/地址', component:组件 }
]

定义了命名路由,那么就可以通过路由名称实现跳转

// 原来写法
<router-link to='/地址'></router-link>
// 新写法
<router-link :to="{name:'名称'}"></router-link>

// 编程式导航
router.push('/地址')
router.push({name:'名称'})
55、如何定义动态路由,获取动态参数
// 映射关系
const routes = [
	{ name:'名称' ,path:'/地址/:标识符', component:组件 }
]
<router-link to='/地址/数据'></router-link>
<router-link :to="{name:'名称',params:{标识符:数据}}"></router-link>

// 编程式导航
router.push('/地址/数据')
router.push({name:'名称',params:{标识符:数据}})

获取动态参数

动态路由的参数,使用“”冒号开头,当匹配到一个路径时,参数会被设置到this.$router.params中,并且可以在每个组建中使用

this.$router.push({
  name:'路由地址',
  params:{
    name:'要发送的数据'
  }
})
// 读取路由参数接收
this.name=this.$route.params.name

this.$route.params.标识符 得到 数据

query 和 params 之间的区别是什么
  1. query 要用 path 来引入,params 需要用 name 来引入
  2. 接收参数时,分别this.route.query.name和this.route.params.name (route而不是router)
  3. query 更加类似于我们 ajax 中 get 传参,params 则类似于 post,前者在浏览器的地址中显示,params 不显示
  4. params 传值一刷新就没了,query 传值刷新还存在

但是这里我们有更加高级的用法,使用props将组件解耦

// 映射关系
const routes = [
	{ name:'名称' ,path:'/地址/:标识符', component:组件,props:true }
  // // 开启props传参,说白了将路由参数传递到组件的props中
]
// 组件中,下面是组件的配置选型
export default {
	...,
	props:['标识符']   // props定义和动态路由后面标识符一致的定义属性。
	...
}
// 组件中可以直接通过    this.标识符  直接获得路由参数数据。

如案例:

// 路由配置
const router = new VueRouter({
    routes: [
        { 
            path: '/user/:id', 
            component: User, 
            props: true
       }
   ]})
// A页面
this.$router.push('/user/123') 
或者
<router-link to='/user'>去user页面</router-link>
// User页面
const User = {
    props: ['id'],   // this.id 就是动态路由数据值为 123
    template: '<div>User {{ id }}</div>'    // 这里就是会渲染123
}
56、命名视图及使用
  • 所谓的命名视图的意思是默认情况下我们一个路由地址渲染一个组件到一个router-view里面去;而命名视图的目的是 可以实现一个路由地址,渲染多个组件到不同的router-view里去进行展示

  • 使用:

    // 映射关系
    const routes = [
    	{ 
    		name:'名称' ,
    		path:'/地址', 
    		//component:组件    //只能渲染一个组件
            components:{
                default:组件X
                视图名A:组件A,
                视图名B:组件B
            }
    	}
    ]
    
    <router-view></router-view>   <!--默认视图,渲染组件X-->
    上面这句其实是:
    <router-view name='default'></router-view>
    
    <router-view name="视图名A"></router-view>     <!--渲染组件A-->
    <router-view name="视图名B"></router-view>     <!--渲染组件B-->
    
57、嵌套路由
  • 定义:所谓的嵌套路由,指的的是在一个路由组件里面还有会有router-view展示下一级的路由组件信息。

  • 何时使用:我们开发一些特定功能模块的时候,包含有子模块页面,就可以采用嵌套路由。 如:用户中心是一个大模块页面,用户订单、用户资料、收货地址等等都会有侧边栏信息,诸如此类有共同模块布局,且内容不同的,则可以使用嵌套路由。

// 映射关系
const routes = [
	{ 
		name:'名称' ,
		path:'/地址', 
		component:组件    
 		children:[   // 子级路由会渲染上级路由的组件里面的router-view里面进行展示
 			{ name:'名称1', path:"/地址/子地址1",  component: ()=>import(组件地址) },
 			{ name:'名称2', path:"/地址/子地址2",  component: ()=>import(组件地址) },
            // 子级的地址也可以不加  /地址,  这样会自动拼接 上一级的前缀。 注意不能加 /
 			{ name:'名称3', path:"子地址3",  component: ()=>import(组件地址) }
			// 上述  path就单独写一个  子地址3  就表示   /地址/子地址3
 		]
	}
]
58、vue-router有哪几种导航钩子?

三种

第一种是全局导航钩子:

  • router.beforeEach( (to,from,next)=>{ ... } )
  • router.afterEach((to, from) => { ... })

第二种:组件内的钩子

  • beforeRouteEnter(to, from, next) { ... }
  • beforeRouteUpdate (to, from, next) {...}
  • beforeRouteLeave (to, from, next) {...}

第三种:单独路由独享组件

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {  // 配置在路由映射里面
        // ...
      }
    }
  ]
})
59、路由元信息是什么?有何作用?
  • 设置每个路由映对象的时候,可以增加一个meta属性,里面可以自定义相关数据
// 路由映射
{
	path:'/地址',
	component: ()=>import(组件地址),
	meta:{
	 	属性名:"属性值",
	 	....
	}
}
  • 组件中读取: this.$route.meta.属性名

  • 作用:

    • 可以通过这个设置标题,在拦截器里面进行设置;
    • meta里面放入权限验证字段,判断是否有权限访问页面
60、如何实现页面过度
<transition>
  <router-view></router-view>
</transition>
61、导航钩子有何作用?
  • 导航钩子类似中间件的味道,对导航的发生过程进行拦截,满足条件才可以继续向下走,否则路由就会被挂起来。 所以我们很多时候需要注意是否需要在导航守卫里面调用next

  • 全局前置守卫:(router.beforeEach((to, from, next) => { ... }

    • 权限判断、标题设置、进度条开始 等,需要所有路由都做的事情
  • 全局守后置守卫:(router.afterEach((to, from) => {...}

    • 进度条结束等
  • 路由独享守卫:( beforeEnter: (to, from, next) => { ... }

    • 权限判断、针对某一个路由要做的事情都放在这里。
  • 局部前置守卫: (beforeRouteEnter (to, from, next) { ... }

    • 进入页面之前要做的事情,注意这里不能使用this,因为组件还没有初始化
  • 局部更新守卫: (beforeRouteUpdate (to, from, next) { ... }

    • 同路由跳转同路由的更新监测,如详情页跳转到详情页,可以放在这里监听
  • 局部后置守卫: (beforeRouteLeave (to, from, next) { ... }

    • 某个页面要消失的时候做的事情,如判断是否要离开,是否保存了数据等
62、如何处理滚动行为
  • history模式下
new VueRouter({
	...,
     // savedPosition 这个参数当且仅当导航 (通过浏览器的 前进/后退 按钮触发) 时才可用  效果和 router.go() 或 router.back()
	scrollBehavior (to, from, savedPosition) {
      // 返回savedPosition 其实就是 当用户点击 返回的话,保持之前游览的高度
      if (savedPosition) {
        return savedPosition
      } else {
        return { x: 0, y: 0 }
      }
    },
    ...
})

注意:scrollBehavior 属性仅在history模式下才可以使用

hash模式下

router.beforeEach((to,from,next)=>{
	// 直接使用window的scroll方法滚动回顶部
	window.scrollTo(0,0)
	next()
})
63、如何实现刷新页面
  • 实现思路有3种
  • 方式1:
    • 通过给router-view添加v-if指令,控制组件重新渲染,从而实现刷新。
  • 方式2:
    • 采用window.reload(),或者router.go(0)刷新时,整个浏览器进行了重新加载,闪烁,体验不好
  • 方式3:
    • 为了实现刷新页面,可以先跳转到一个空页面,然后马上跳回来,从而实现这个功能

  • provide / inject 组合

    作用:允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

  • App.vue: 声明reload方法,控制router-view的显示或隐藏,从而控制页面的再次加载

  • **某个组件中:**在页面注入App.vue组件提供(provide)的 reload 依赖,在逻辑完成之后(删除或添加…),直接this.reload()调用,即可刷新当前页面

64、完整的 vue-router 导航解析流程

1.导航被触发;
2.在失活的组件里调用beforeRouteLeave守卫;
3.调用全局beforeEach守卫;
4.在复用组件里调用beforeRouteUpdate守卫;
5.调用路由配置里的beforeEnter守卫;
6.解析异步路由组件;
7.在被激活的组件里调用beforeRouteEnter守卫;
8.调用全局beforeResolve守卫;
9.导航被确认;
10…调用全局的afterEach钩子;
11.DOM更新;
12.用创建好的实例调用beforeRouteEnter守卫中传给next的回调函数

65、切换路由时,需要保存草稿的功能,怎么实现
<keep-alive :include="include">
    <router-view></router-view>
 </keep-alive>

其中include可以是个数组,数组内容为路由的name选项的值

66、说说你对router-link的了解

<router-link>是Vue-Router的内置组件,在具有路由功能的应用中作为声明式的导航使用。

<router-link>有8个props,其作用是:

  • to:必填,表示目标路由的链接。当被点击后,内部会立刻把to的值传到router.push(),所以这个值可以是一个字符串或者是描述目标位置的对象。
<router-link to="home">Home</router-link>
<router-link :to="'home'">Home</router-link>
<router-link :to="{ path: 'home' }">Home</router-link>
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
<router-link :to="{ path: 'user', query: { userId: 123 }}">User</router-link>
  • 注意path存在时params不起作用,只能用query
  • replace:默认值为false,若设置的话,当点击时,会调用router.replace()而不是router.push(),于是导航后不会留下 history 记录。
  • append:设置 append 属性后,则在当前 (相对) 路径前添加基路径。
  • tag:让<router-link>渲染成tag设置的标签,如tag:'li,渲染结果为<li>foo</li>
  • active-class:默认值为router-link-active,设置链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。
  • exact-active-class:默认值为router-link-exact-active,设置链接被精确匹配的时候应该激活的 class。默认值可以通过路由构造函数选项 linkExactActiveClass 进行全局配置的。

:是否精确匹配,默认为false

<!-- 这个链接只会在地址为 / 的时候被激活 -->
<router-link to="/" exact></router-link>

event:声明可以用来触发导航的事件。可以是一个字符串或是一个包含字符串的数组,默认是click

67.路由传参

传参方式可划分为params 传参 和query传参,而params传参可以分为在url地址栏当中显示参数和不显示参数俩种方式

query 传参(显示参数)也可分为声明式和编程式两种方式**,两者的接收方式都是:this.$route.query.id**

  • 该方式是通过 router-link 组件的 to 属性实现,不过使用该方式传值的时候,需要子路由提前配置好路由别名

    {path:'child',
     name:'Child,
     component:Child
     }
     <router-link :to="{name:'Child',query:{id:1}}"></router-link>
    
  • 编程式 this.$router.push:使用该方式传值的时候,同样需要子路由提前配置好路由别名(name 属性)

​ this,$router.push({

​ name:‘Child’,

​ query:{

​ id:1}

​ })

params 传参(显示参数)又可分为 声明式 和 编程式 两种方式,接收:this.$route.params.id

  • 声明式router-link:该方式是通过router-link组件的to属性实现,子路由需要提前配置好参数

    <router-link :to="/child/1">跳转到子路由</router-link>
    {
      path:'/child/:id',
      component:Child
    }
    

    编程式 this.$router.push:同样需要子路由提前配置好参数

    {
      path:'/child/:id',
      component:Child
    }
    this.$router,push({
      path:'/child/${id}'
    })
    

    params传参(不显示参数)也可分为声明式和编程式两种方式,与显示参数不同的是,这里是通过路由的别名 name 进行传值的(只能通过name 不能通过path)

    <router-link :to="{name:'Child',params:{id:1}}">跳转到子路由</router-link>
    {
      path:'/child',
      name:'Child',
      component:Child
    }
    this.$router.push({
      name:'Child',// 只能用name 不能用path
      params:{
        id:1
      }
    })
    
68.router 路由懒加载

也叫延迟加载,即在需要的时候进行加载,随用随载。

为什么需要懒加载,像vue这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,时间过长,会出啊先长时间的白屏,即使做了loading也是不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时 简单的说就是:进入首页不用一次加载过多资源造成用时过

 解决方案-插件 vuex-persistedstate

Vuex

69.Vuex中的核心特性

Vuex是一个专门为Vue.js应用程序开发的状态管理模式,

4.1 state: State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储

​ 在组件中访问State的方式:

​ (1) this.$$ store.state .全局数据名称 ,如:this.$store.state.count

​ (2) //把全局数据映射为当前组件的计算属性.

​ 先按需导入mapState函数: import { mapState } from ‘vuex’

​ 然后数据映射为计算属性: computed:{ …mapState([‘全局数据名称’]) },然后直接使用

4.2 Mutation: Mutation用于修改变更$store中的数据

使用Mutation的第一种方式:使用commit调用mutation方法

​ **this.$store.commit() **调用mutations中的对应函数

打开store.js文件

mutations: {
    add(state,step){
      // 第一个形参是state也就是 $state对象,第二个形参是调用add时传递的参数
      state.count+=step;
    }
  }

然后在Addition.vue中给按钮添加事件代码:

<button @click="Add">+1</button>
methods:{
  Add(){
    //使用commit函数调用mutations中的对应函数,
    //第一个参数就是我们要调用的mutations中的函数名 第二个参数就是传递给add函数的参数  
    this.$store.commit('add',10)
  }
}

使用Mutation的第二种方式: 把全局的mutation方法映射为当前组件的methods方法

​ import { mapMutations } from ‘vuex’

​ methods:{ …mapMutations([‘add’])}

import {mapMutations } from 'vuex'
export default {
  methods:{
      //获得mapMutations映射的sub函数
      ...mapMutations(['sub']),
      //当点击按钮时触发Sub函数
      Sub(){           //调用sub函数完成对数据的操作  
          this.sub(10);
      }
  },
}
70.组件中如何获取带有命名空间moduleA中的state数据?

(1)基本方法:this.$store.state.moduleA.countA

(2) mapState辅助函数方式:

…mapState(

{

count:state=>state.moduleB.countB

})

71 .组件中如何调用命名空间模块中的getters

(1) commonGetter(){

this.$store.getters[‘moduleA/moduleAGetter’]

}

(2)…mapGetters(‘moduleA’,[‘moduleAGetter’]), 此处的moduleA,不是以前缀的形式出现

(3)别名状态下

…mapGetters([

paramGetter:‘moduleA/moduleAGetter’

])

72. vuex的优缺点是什么****

优点:

  • 能够在vuex中,集中管理共享的数据,易于开发和后期维护;

  • Vuex 的状态存储是响应式的,当 Vue 组件从 store中读取状态的时候,若 store 中的状态发生变化,能够触发响应式的渲染页面更新 (localStorage就不会),那么相应的组件也会相应地得到高效更新。

  • js 原生的数据对象写法, 比起 localStorage 不需要做转换, 使用方便

  • 限定了一种可预测的方式改变数据, 避免大项目中, 数据不小心的污染

    缺点

    • 刷新浏览器,vuex中的state会重新变为初始状态 ;
73. vuex里面的数据为什么是响应式的

它是可以独立的提供响应式数据的,它和组件没有强相关的关系是一个单向的数据流。vuex提供数据驱动我们的视图View Component,视图通过Dispatch派发我们的Actions。我们在Actions中可以进一步的做我们的异步操作,可以通过ajax接口去后端获取想要的数据,然后通过Commit的形式,提交给我们的Mutations,由Mutations来最终更改我们的State。
那么为什么要经过Mutations呢?这主要我们要在Devtools里面记录我们的数据的变化。在插件中记录我们数据的变化可以进一步的调试。所以说Mutations需要一个同步的操作。如果说你有异步的操作,就需要在Actions中处理

若果你没有异步的操作可以直接由组件,会接commit到Mutations

74怎么引用Vuex
  • 先安装依赖nnpm install vuex --save
  • 在项目目录src中建立store文件夹
  • 在store文件夹下新建index.js文件
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
//不是在生产环境debug为true
const debug = process.env.NODE_ENV !== 'production';
//创建Vuex实例对象
const store = new Vuex.Store({
    strict:debug,//在不是生产环境下都开启严格模式
    state:{
    },
    getters:{
    },
    mutations:{
    },
    actions:{
    }
})
export default store;

然后再main.js文件中引入Vuex

import Vue from 'vue';
import App from './App.vue';
import store from './store';   
const vm = new Vue({
    store:store,  // 一旦挂载,组件中this.$store就可以得到仓库实例
    render: h => h(App)
}).$mount('#app')
75Vuex中状态是对象时,使用时要注意什么

因为对象是引用类型,复制后改变属性还是会影响原始数据,这样会改变state里面的状态,是不允许,所以先用深度克隆复制对象,再修改

76怎么在组件中批量使用Vuex的state状态

使用mapState辅助函数, 利用对象展开运算符将state混入computed对象中

import {mapState} from 'vuex'
export default{
    computed:{
        ...mapState(['price','number'])
    }
}
77、怎么通过getter来实现在组件内可以通过特定条件来获取state的状态

通过让getter返回一个函数,来实现给getter传参,然后通过参数来进行判断从而获取state中满足要求的状态

const store = new Vuex.Store({
    state: {
        todos: [
            { id: 1, text: '...', done: true },
            { id: 2, text: '...', done: false }
        ]
    },
    getters: {
        getTodoById: (state) => (id) =>{
            return state.todos.find(todo => todo.id === id)
        }
    },
})

然后在组件中可以用计算属性computed通过this.$store.getters.getTodoById(2)这样来访问这些派生转态

computed: {
    getTodoById() {
        return this.$store.getters.getTodoById
    },
}
mounted(){
    console.log(this.getTodoById(2).done)//false
}
78、怎么在组件中批量给Vuex的getter属性取别名并使用

使用mapGetters辅助函数, 利用对象展开运算符将getter混入computed 对象中

import {mapGetters} from 'vuex'
export default{
    computed:{
        ...mapGetters({
            myTotal:'total',
            myDiscountTotal:'discountTotal',
        })
    }
}
79、在Vuex的state中有个状态number表示货物数量,在组件怎么改变它

首先要在mutations中注册一个mutation

const store = new Vuex.Store({
    state: {
        number: 10,
    },
    mutations: {
        SET_NUMBER(state,data){
            state.number=data;
        }
    },
})

在组件中使用this.$store.commit提交mutation,改变number

this.$store.commit('SET_NUMBER',10)
80、在组件中多次提交同一个mutation,怎么写使用更方便。

使用mapMutations辅助函数,在组件中这么使用

import { mapMutations } from 'vuex'
methods:{
    ...mapMutations({
        setNumber:'SET_NUMBER',
    })
}

然后调用this.setNumber(10)相当调用this.$store.commit('SET_NUMBER',10)

81、Vuex中action通常是异步的,那么如何知道action什么时候

在action函数中返回Promise,然后再提交时候用then处理

actions:{
    SET_NUMBER_A({commit},data){
        return new Promise((resolve,reject) =>{
            setTimeout(() =>{
                commit('SET_NUMBER',10);
                resolve();
            },2000)
        })
    }
}
this.$store.dispatch('SET_NUMBER_A').then(() => {
  // ...
})
82、Vuex中有两个action,分别是actionA和actionB,其内都是异步操作,在actionB要提交actionA,需在actionA处理结束再处理其它操作,怎么实现

利用ES6的asyncawait来实现

actions:{
    async actionA({commit}){
        //...
    },
    async actionB({dispatch}){
        await dispatch ('actionA')//等待actionA完成
        // ... 
    }
}
83、在模块中,getter和mutation和action中怎么访问全局的state和getter
  • 在getter中可以通过第三个参数rootState访问到全局的state,可以通过第四个参数rootGetters访问到全局的getter。

  • 在mutation中不可以访问全局的satat和getter,只能访问到局部的state。

  • 在action中第一个参数context中的context.rootState访问到全局的state,context.rootGetters访问到全局的getter。

84、在组件中怎么访问Vuex模块中的getter和state,怎么提交mutation和action
  • 直接通过this.$store.getters.xxxthis.$store.模块名.xxx来访问模块中的getter和state。
  • 直接通过this.$store.commit('mutationA',data)提交模块中的mutation
  • 直接通过this.$store.dispatch('actionA',data)提交模块中的action
  • 以上是没有命名空间的,如果有命名空间那么就是下面这样
  • 直接通过this.$store.getters['模块名/xxx']this.$store.模块名.xxx来访问模块中的getter和state
  • 直接通过this.$store.commit('模块名/mutationA',data)提交模块中的mutation
  • 直接通过this.$store.dispatch('模块名/actionA',data)提交模块中的action
85、怎么在带命名空间的模块内提交全局的mutation和action

{ root: true } 作为第三参数传给 dispatch 或 commit 即可

this.$store.dispatch('actionA', null, { root: true })
this.$store.commit('mutationA', null, { root: true })
86、组件中怎么提交modules中的带命名空间的moduleA中的mutationA
this.$store.commit('moduleA/mutationA',data)
87、怎么使用mapState,mapGetters,mapActions和mapMutations这些函数来绑定带命名空间的模块

使用createNamespacedHelpers创建基于某个命名空间辅助函数

import { createNamespacedHelpers } from 'vuex';
const { mapState, mapActions,mapGetters,mapMutations } = createNamespacedHelpers('moduleA');
export default {
    computed: {
        // 在 `module/moduleA` 中查找
        ...mapState({
            a: state => state.a,
            b: state => state.b
        }),
        ...mapGetters(['getterA','getterB'])
    },
    methods: {
        // 在 `module/moduleA` 中查找
        ...mapActions([
            'actionA',
            'actionB'
        ]),
        ...mapMutations([
        	'mutationA',
        	'mutationB'
        ])
    }
}

如果不使用createNamespacedHelpers就需要注意,不能直接用数组语去获取

import { mapState, mapActions } from 'vuex';
export default {
    computed: {
        // 在 `module/moduleA` 中查找
        ...mapState({
            a: state => state.模块名.a,
            b: state => state.模块名.b
        }),
        ...mapGetters({
            gettersA:'模块名/gettersA'
            gettersB:'模块名/gettersB'
        })
    },
    methods: {
        // 在 `module/moduleA` 中查找
        ...mapActions({
            actionA:'模块名/actionA',
            actionB:'模块名/actionB'
        }),
        ...mapMutations({
            mutationA:'模块名/mutationA',
            mutationB:'模块名/mutationB'
        })
    }
}
88、在v-model上怎么用Vuex中state的值

需要通过computed计算属性来转换

<input v-model="message">
// ...
computed: {
    message: {
        get () {
            return this.$store.state.message
        },
        set (value) {
            this.$store.commit('updateMessage', value)
        }
    }
}
89、为什么不推荐直接赋值修改state

组件里直接修改state也是生效的,但是不推荐这种直接修改state的方式,因为这样不能使用vuex的浏览器插件来跟踪状态的变化,不利于调试。如果是严格模式下,直接会抛出错误。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值