Vue.js 面试题大全

文章目录


一、Vue概念(特点、原理)

1.Vue的优点

  • 轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十 kb ;
  • 简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
  • 双向数据绑定:保留了 angular 的特点,在数据操作方面更为简单;
  • 组件化:保留了 react 的优点,实现了 html 的封装和重用,在构建单页面应用方面有着独特的优势;
  • 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
  • 虚拟DOM:dom 操作是非常耗费性能的, 不再使用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种方式;
  • 运行速度更快:相比较于 react 而言,同样是操作虚拟 dom ,就性能而言, vue 存在很大的优势。

2.说说你对SPA单页面的理解,它的优缺点分别是什么?

SPA( single page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转,而页面的变化是利用路由机制实现 HTML 内容的变换,避免页面的重新加载。

优点

  • 用户体验好+减轻服务器压力,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染。
  • 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理。

缺点

  • 初次加载耗时多
  • 不能使用浏览器的前进后退功能,由于单页应用在一个页面中显示所有的内容,所以,无法前进后退
  • 不利于搜索引擎检索:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

3. SPA首屏加载速度慢的怎么解决?

首屏时间(First Contentful Paint),指的是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间,此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容;

加载慢的原因

  • 网络延时问题
  • 资源文件体积是否过大
  • 资源是否重复发送请求去加载了
  • 加载脚本的时候,渲染内容堵塞了

常见的几种SPA首屏优化方式

  • 减小入口文件积
  • 静态资源本地缓存
  • UI框架按需加载
  • 图片资源的压缩
  • 组件重复打包
  • 开启GZip压缩
  • 使用SSR

4. Vue初始化过程中(new Vue(options))都做了什么?

  1. 处理组件配置项;初始化根组件时进行了选项合并操作,将全局配置合并到根组件的局部配置上;初始化每个子组件时做了一些性能优化,将组件配置对象上的一些深层次属性放到 m.$options 选项中,以提高代码的执行效率;
  2. 初始化组件实例的关系属性,比如parent、children、root、refs
  3. 处理自定义事件
  4. 调用 beforeCreate 钩子函数
  5. 初始化组件的 inject 配置项,得到 ret[key] = val 形式的配置对象,然后对该配置对象进行响应式处理,并代理每个 key 到 vm 实例上
  6. 数据响应式,处理 props、methods、data、computed、watch 等选项
  7. 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
  8. 调用 created 钩子函数
  9. 如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了 el 选项,就不需要再手动调用 $mount 方法,反之,没提供 el 选项则必须调用 $mount
  10. 接下来则进入挂载阶段
// core/instance/init.js
export function initMixin (Vue: Class<Component>) {
    
    
  Vue.prototype._init = function (options?: Object) {
    
    
      const vm: Component = this
      vm._uid = uid++
      
      // 如果是Vue的实例,则不需要被observe
      vm._isVue = true
      
      if (options && options._isComponent) {
    
    
        // optimize internal component instantiation
        // since dynamic options merging is pretty slow, and none of the
        // internal component options needs special treatment.
        initInternalComponent(vm, options)
      } else {
    
    
        vm.$options = mergeOptions(
          resolveConstructorOptions(vm.constructor),
          options || {
    
    },
          vm
        )
      }
      
      if (process.env.NODE_ENV !== 'production') {
    
    
        initProxy(vm)
      } else {
    
    
        vm._renderProxy = vm
      }

      vm._self = vm

      initLifecycle(vm)
      initEvents(vm)
      callHook(vm, 'beforeCreate')
      initInjections(vm) // resolve injections before data/props

      initState(vm)
      initProvide(vm) // resolve provide after data/props
      callHook(vm, 'created')
      
      if (vm.$options.el) {
    
    
        vm.$mount(vm.$options.el)
      }
  }
}

5. 对MVVM的理解?

MVVM 由 Model、View、ViewModel 三部分构成

  • Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;
  • View 代表UI 组件,它负责将数据模型转化成UI 展现出来;
  • ViewModel 是一个同步View 和 Model的对象。

在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。

ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

6. Vue数据双向绑定原理 ★★★★★

实现mvvm的数据双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来给各个属性添加setter,getter并劫持监听,在数据变动时发布消息给订阅者,触发相应的监听回调。就必须要实现以下几点:

1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者

2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

3、实现一个Watcher,作为连接ObserverCompile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
请添加图片描述
请添加图片描述

具体介绍:

如右图所示,new Vue一个实例对象a,其中有一个属性a.b,那么在实例化的过程中,通过Object.defineProperty()会对a.b添加getter和setter,同时Vue.js会对模板做编译,解析生成一个指令对象(这里是v-text指令),每个指令对象都会关联一个Watcher,当对a.b求值的时候,就会触发它的getter,当修改a.b的值的时候,就会触发它的setter,同时会通知被关联的Watcher,然后Watcher就会再次对a.b求值,计算对比新旧值,当值改变了,Watcher就会通知到指令,调用指令的update()方法,由于指令是对DOM的封装,所以就会调用DOM的原生方法去更新视图,这样就完成了数据改变到视图更新的一个自动过程

原理介绍:

大致上是使用数据劫持和订阅发布实现双向绑定。通过实例化一个Vue对象的时候,对其数据属性遍历,通过Object.defineProperty()给数据对象添加setter getter,并对模板做编译生成指令对象,每个指令对象绑定一个watcher对象,然后对数据赋值的时候就会触发setter,这时候相应的watcher对其再次求值,如果值确实发生变化了,就会通知相应的指令,调用指令的update方法,由于指令是对DOM的封装,这时候会调用DOM的原生方法对DOM做更新,这就实现了数据驱动DOM的变化。同时vue还会对DOM做事件监听,如果DOM发生变化,vue监听到,就会修改相应的data.

8 .Vue3.x响应式数据原理

Vue3.x改用Proxy替代Object.defineProperty

因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。

Proxy只会代理对象的第一层,Vue3是怎样处理这个问题的呢?

判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。

监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。

9. Vue3.0 里为什么要用 Proxy API替代 defineProperty API?

1.defineProperty API 的局限性最大原因是它只能针对单例属性做监听。
Vue2.x中的响应式实现正是基于defineProperty中的descriptor,对 data 中的属性做了遍历 + 递归,为每个属性设置了 getter、setter。这也就是为什么 Vue 只能对 data 中预定义过的属性做出响应的原因。

2.Proxy API的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作, 这就完全可以代理所有属性,将会带来很大的性能提升和更优的代码。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

3.响应式是惰性的。
Vue.js 2.x 中,对于一个深层属性嵌套的对象,要劫持它内部深层次的变化,就需要递归遍历这个对象,执行 Object.defineProperty 把每一层对象数据都变成响应式的,这无疑会有很大的性能消耗。
Vue.js 3.0 中,使用 Proxy API 并不能监听到对象内部深层次的属性变化,因此它的处理方式是在 getter 中去递归响应式,这样的好处是真正访问到的内部属性才会变成响应式,简单的可以说是按需实现响应式,减少性能消耗。

10. Proxy 与 Object.defineProperty 优劣对比

1.Proxy 可以直接监听对象而非属性;

2.Proxy 可以直接监听数组的变化;

3.Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;

4.Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;

5.Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;

6.Object.defineProperty 的优势:兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。

11. 为什么vue采用异步渲染 ★★★

vue是组件级更新,当前组件里的数据变了,它就会去更新这个组件。当数据更改一次组件就要重新渲染一次,性能不高,为了防止数据一更新就更新组件,所以做了个异步更新渲染。(核心的方法就是nextTick

源码实现原理:

当数据变化后会调用notify方法,将watcher遍历,调用update方法通知watcher进行更新,这时候watcher并不会立即去执行,在update中会调用queueWatcher方法将watcher放到了一个队列里,在queueWatcher会根据watcher的进行去重,多个属性依赖一个watcher,如果队列中没有该watcher就会将该watcher添加到队列中,然后通过nextTick异步执行flushSchedulerQueue方法刷新watcher队列。flushSchedulerQueue中开始会触发一个before的方法,其实就是beforeUpdate,然后watcher.run() 才开始真正执行watcher,执行完页面就渲染完成啦,更新完成后会调用updated钩子。

简单步骤:

  • 响应式数据更新
  • deep.notify : -> 通知watcher进行更新操作
  • subs[i].update( ) : -> 依次调用watcher的update
  • queueWatcher : -> 将watcher去重放到队列中
  • nextTick(flushSchedulerQueue) : -> 异步刷新watcher队列

12. Vue 的异步更新机制是如何实现的?

  • Vue 的异步更新机制的核心是利用了浏览器的异步任务队列来实现的,首选微任务队列,宏任务队列次之。

  • 当响应式数据更新后,会调用 dep.notify 方法,通知 dep 中收集的 watcher 去执行 update 方法,watcher.update 将 watcher 自己放入一个 watcher 队列(全局的 queue 数组)。

  • 然后通过 nextTick 方法将一个刷新 watcher 队列的方法(flushSchedulerQueue)放入一个全局的 callbacks 数组中。

  • 如果此时浏览器的异步任务队列中没有一个叫 flushCallbacks 的函数,则执行 timerFunc 函数,将 flushCallbacks 函数放入异步任务队列。如果异步任务队列中已经存在 flushCallbacks 函数,等待其执行完成以后再放入下一个 flushCallbacks 函数。

  • flushCallbacks 函数负责执行 callbacks 数组中的所有 flushSchedulerQueue 函数。

  • flushSchedulerQueue 函数负责刷新 watcher 队列,即执行 queue 数组中每一个 watcher 的 run 方法,从而进入更新阶段,比如执行组件更新函数或者执行用户 watch 的回调函数。

13. $nextTick的理解 ★★★

用法:

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

Vue.nextTick( [callback, context] )

// 参数:
{
    
    Function} [callback]
{
    
    Object} [context]

为什么?

Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。

所以为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。

使用场景

在你更新完数据后,需要及时操作渲染好的 DOM时

14. Vue.set 改变数组和对象中的属性

在一个组件实例中,只有在data里初始化的数据才是响应的,Vue不能检测到对象属性的添加或删除,没有在data里声明的属性不是响应的,所以数据改变了但是不会在页面渲染;

解决办法:

使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上

15. vm.$set(obj, key, val) 做了什么?

由于 Vue 无法探测对象新增属性或者通过索引为数组新增一个元素,所以这才有了vm.set,它是Vue.set的别名。vm.set 用于向响应式对象添加一个新的 property,并确保这个新的 property 同样是响应式的,并触发视图更新。

  • 为对象添加一个新的响应式数据:调用 defineReactive 方法为对象增加响应式数据,然后执行 dep.notify 进行依赖通知,更新视图
  • 为数组添加一个新的响应式数据:通过 splice 方法实现

16. vue为什么在 HTML 中监听事件?

你可能注意到这种事件监听的方式违背了关注点分离 (separation of concern) 这个长期以来的优良传统。但不必担心,因为所有的 Vue.js 事件处理方法和表达式都严格绑定在当前视图的 ViewModel 上,它不会导致任何维护上的困难。实际上,使用 v-on 或 @ 有几个好处:

  • 扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。
  • 因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。
  • 当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何清理它们。

二、vue属性

源码定义的initState函数内部执行的顺序:props>methods>data>computed>watch

1. data 数据对象

常见问题:

(1)vue中组件的data为什么是一个函数?而new Vue 实例里,data 可以直接是一个****对象

答案:

我们知道,Vue组件其实就是一个Vue实例。

JS中的实例是通过构造函数来创建的,每个构造函数可以new出很多个实例,那么每个实例都会继承原型上的方法或属性。

Vue的data数据其实是Vue原型上的属性,数据存在于内存当中。Vue为了保证每个实例上的data数据的独立性,规定了必须使用函数,而不是对象。

因为使用对象的话,每个实例(组件)上使用的data数据是相互影响的,这当然就不是我们想要的了。对象是对于内存地址的引用,直接定义一个对象的话组件之间都会使用这个对象,这样会造成组件之间数据相互影响。

使用函数后,使用的是data()函数,data()函数中的this指向的是当前实例本身,就不会相互影响了。

而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。

(2) vue中data的属性可以和methods中方法同名吗,为什么?
答案:

同名,methods的方法名会被data的属性覆盖;调试台也会出现报错信息,但是不影响执行;

原因:源码定义的initState函数内部执行的顺序props>methods>data>computed>watch

//initState部分源码
export function initState (vm: Component) {
    
    
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    
    
    initData(vm)
  } else {
    
    
    observe(vm._data = {
    
    }, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    
    
    initWatch(vm, opts.watch)
  }   
}

2. Directives 指令对象

a. 作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
b. 构成

  • 参数v-bind/on:href="url";
  • 动态参数v-bind/on:[eventName]="doSomething";
  • 修饰符 (modifier) :v-on:submit.prevent="onSubmit";
    常见修饰符:
    .prevent: 提交事件不再重载页面;
    .stop: 阻止单击事件冒泡;
    .self: 当事件发生在该元素本身而不是子元素的时候会触发;
    .capture: 事件侦听,事件发生的时候会调用
  • 缩写:href="url" @click="doSomething"
    c. 常见指令v-for 、 v-if 、v-bind、v-on、v-show、v-else、v-model、v-once
    d. 自定义指令
  • 分类:
    1. 全局指令:通过 Vue.directive() 函数注册一个全局的指令。
    语法:Vue.directive(directiveName, 配置对象/回调函数)
    2. 局部指令:通过组件的 directives 对象属性,对该组件添加一个局部的指令。
    语法:directives: {directiveName: { }}
  • 指令钩子函数:
    • 分类:
      1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
      2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
      3. update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 。
      4. componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
      5. unbind:只调用一次,指令与元素解绑时调用。
    • 参数:
      1. el 绑定的元素*
      2. binding 对象
      a. name 指令名*,不包括 v- 前缀
      b. value 指令的绑定值*(作为表达式解析后的结果)
      c. oldvalue 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用,无论值是否改变都可用
      d. expression 指令绑定的表达式(字符串)
      e. arg 传给指令的参数,可选
      f. modifiers 传给指令的修饰符组成的对象,可选,每个修饰符对应一个布尔值
      3. vnode
      4. oldnode
    • 应用场景
      问题:你有写过自定义指令吗?自定义指令的应用场景有哪些?
      防抖、图片懒加载、拖拽功能、一键复制功能、处理失败图片、根据权限控制按钮的显示与隐藏、输入框自动聚焦、下拉菜单(点击下拉菜单本身不会隐藏菜单、点击下拉菜单以外的区域隐藏菜单)、相对时间转换(类似微博、朋友圈发布动态后的相对时间,比如刚刚、两分钟前等等)

常见问题:

  1. v-if 和 v-show的区别
  • 相同点:v-show和v-if都能控制元素的显示和隐藏。
  • 不同点:
    • 实现本质方法不同:v-show本质就是通过设置css中的display设置为none;控制隐藏v-if是动态的向DOM树内添加或者删除DOM元素;
    • v-show都会编译,初始值为false,只是将display设为none,但它也编译了;v-if初始值为false,就不会编译了
      总结:v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,如果要频繁切换某节点时,故v-show性能更好一点。
  1. 避免 v-if 和 v-for 一起使用,永远不要在一个元素上同时使用 v-if 和 v-for
  • 优先级:vue2.x版本中, v-for 的优先级比 v-if 的高,所以每次渲染时都会先循环再进行条件判断,而又因为 v-if 会根据条件为 true 或 false来决定渲染与否的,所以如果将 v-if 和 v-for一起使用时会特别消耗性能,如果有语法检查,则会报语法的错误。
  • 解决方案
  1. 将 v-if 放在外层嵌套 template (页面渲染不生成 DOM节点),在这一层进行 v-if 判断,然后在内部进行 v-for 循环
  2. 如果条件出现在循环内部,不得不放在一起,可先对数据在计算属性数据中进行过滤,然后再进行遍历渲染;

vue2.x版本中,当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级;
vue3.x版本中,当 v-if 与 v-for 一起使用时,v-if 具有比 v-for 更高的优先级。

  1. 虚拟DOM中key的作用
  • 简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
  • 复杂的说:当状态中的数据发生了变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
  • 在旧虚拟DOM中找到与新虚拟DOM相同的key
    • 内容没有变化,直接使用原来的真实DOM
    • 内容发生改变,替换掉之前旧的虚拟DOM,生成新的真实DOM
  • 在旧虚拟DOM中未找到与新虚拟DOM相同的key
    • 直接生成新的真实DOM
  1. 用index作为key可能会引发的问题
  • 若对数据进行:逆序添加/逆序删除等破坏顺序的操作,会产生没有必要的真实DOM更新,界面效果虽然没有问题,但是数据过多的话,会效率过低;
  • 如果结构中还包含输入类的DOM,会产生错误DOM更新,界面有问题;
  • 注意!如果不存在对数据的逆序操作,仅用于渲染表用于展示,使用index作为key是没有问题的。

3. computed 计算属性对象

  1. 概念:计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
  2. 构成:
    1. getter
    2. setter
computed: {
    
    
  fullName: {
    
    
    // getter
    get: function () {
    
    
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
    
    
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
  1. 特点:
    1. 使得数据处理结构清晰;
    2. 依赖于数据,数据更新,处理结果自动更新;
    3. 计算属性内部this指向vm实例;
    4. 在template调用时,直接写计算属性名即可;
    5. 常用的是getter方法,获取数据,也可以使用set方法改变数据;
    6. 相较于methods,不管依赖的数据变不变,methods都会重新计算,但是依赖数据不变的时候computed从缓存中获取,不会重新计算。

4. methods 方法属性对象

5. watch 侦听属性对象

a. 在vue中,使用watch来监听数据的变化(监听机制+事件机制);

  • 1.监听的数据后面可以写成对象形式,包含handler方法,immediatedeep
  • 2.immediate表示在watch中首次绑定的时候,是否执行handler,值为true则表示在watch中声明的时候,就立即执行handler方法,值为false,则和一般使用watch一样,在数据发生变化的时候才执行handler。
  • 3.当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。

b. 注销:unWatch(); // 手动注销watch

watch: {
    
    
  // 第一种方式:监听整个对象,每个属性值的变化都会执行handler
  // 注意:属性值发生变化后,handler执行后获取的 newVal 值和 oldVal 值是一样的
  food: {
    
    
    // 每个属性值发生变化就会调用这个函数
    handler(newVal, oldVal) {
    
    
      console.log('oldVal:', oldVal)
      console.log('newVal:', newVal)
    },
    // 立即处理 进入页面就触发
    immediate: true,
    // 深度监听 属性的变化
    deep: true
// 基本数据
// 对象和数组都是引用类型,引用类型变量存的是地址,地址没有变,所以不会触发watch。这时我们需要进行深度监听,就需要加上一个属性 deep,值为 true
            },
  // 第二种方式:监听对象的某个属性,被监听的属性值发生变化就会执行函数
  // 函数执行后,获取的 newVal 值和 oldVal 值不一样
  'food.name'(newVal, oldVal) {
    
    
      console.log('oldVal:', oldVal)   // 冰激凌
      console.log('newVal:', newVal)   // 棒棒糖
  }
}
// watch监听路由
watch: {
    
    
    changeShowType(value) {
    
    
      console.log("-----"+value);
    },
    '$route'(to,from){
    
    
      console.log(to);   //to表示去往的界面
      console.log(from); //from表示来自于哪个界面
      if(to.path=="/shop/detail"){
    
    
        console.log("商品详情");
      }
    }
  },

Watch和computed的区别

  • computed支持缓存,只有依赖数据发生改变,才会重新进行计算; 而watch不支持缓存,数据变, 直接会触发相应的操作
  • computed不支持异步,当computed内有异步操作时无效,无法监听数据的变化,而watch支持异步
  • computed属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值; 而watch监听的函数接收两个参数,第一个参数是最新的值,第二个参数是输入之前的值
  • 如果一个属性是由其它属性计算而来的,这个属性依赖其它属性,多对一(一个数据受多个数据影响)或者一对一,一般用computed;而当一个属性发生变化时,需要执行对应的操作,一对多(一个数据影响多个数据),一般用watch。

6. 生命周期钩子对象

a. 定义:生命周期通俗说就是Vue实例从创建到销毁的过程,就是生命周期。
b. 分类:创建-挂载-更新-销毁

  • beforeCreate(创建前) :组件实例被创建之初,组件的属性生效之前

    • 特点:beforeCreate生命周期执行的时候,data和methods中的数据都还没有初始化。不能在这个阶段使用data中的数据和methods中的方法
    • 应用:开启加载一个动画
  • created(创建后):组件实例已完成创建,属性也已绑定,但真实 dom 还没有生成,$el 还不可用。

    • 特点:data 和 methods都已被初始化,如要调用 methods 中的方法,或操作 data 中的数据,最早可在这个阶段中操作
    • 应用:网路请求,**注意:调用methods中的方法,完成网络请求,vue推荐的两种数据获取方式
    • 1、beforeRouteEnter 该路由加载之前请求数据,数据请求完成执行next(),显示目标路由(优点:保证页面显示时,一定可以拿到数据)

    • 2、created,在该时机发起请求,获取数据。

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

    • 特点:执行到这个钩子的时候,在内存中已编译好模板,但还没有挂载到页面中,此时,页面还是旧的
    • 应用:开发中很少使用
  • mounted(挂载后):在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子

    • 特点:Vue实例已经初始化完成。此时组件脱离了创建阶段,进入到了运行阶段。 如要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行
    • 应用:操作dom**
  • beforeUpdate(更新前):组件数据更新之前调用,真实DOM还没被渲染

    • 特点:当执行这个钩子时,页面中显示的数据还是旧的,data中的数据是更新后的,页面还没有和最新的数据保持同步
  • update(更新后) :组件数据更新之后

    • 特点:页面显示的数据和data中的数据已保持同步,都是最新的
    • 应用:这些钩子的触发频率很高,极少操作。
  • activated(激活前) :keep-alive专属,组件被激活时调用

    • 特点:当组件被切回来时,再去缓存里找这个组件、触发 activated钩子函数。
  • deactivated(激活后-没有被激活) :keep-alive专属,组件被销毁时调用

    • 特点:当组件被换掉时,会被缓存到内存中、触发 deactivated 生命周期
  • beforeDestory(销毁前) :组件销毁前调用

    • 特点:Vue实例从运行阶段进入到销毁阶段,此时所有的 data 和 methods , 指令, 过滤器 ……都处于可用状态。还没有真正被销毁
  • destoryed(销毁后):组件销毁前调用

    • 特点:此时所有的 data 和 methods , 指令, 过滤器 ……都处于不可用状态。组件已经被销毁。
    • 应用:移除耗时操作,比如计时器等。
  • errorCaptured(错误调用):当捕获一个来自后代组件的错误时被调用

注意:面试官想听到的不只是你说出了以上八个钩子名称,而是每个阶段做了什么?可以收藏下图!
请添加图片描述

3. Vue子组件和父组件执行顺序

  1. 加载渲染过程beforeCreate(父) —> created(父)—>beforeMount(父)—>beforeCreate(子)—>created(子)—>beforeMount(子)—>mounted(子)—>mounted(父)
  2. 更新过程beforeUpdate(父) —> beforeUpdate(子) —> update(子) —> update(父)
  3. 父组件更新beforeUpdate(父) —> updated(父)
  4. 销毁过程beforeDestory(父) —> beforeDestory(子) —> destoryed(子) —> destoryed(父)
  5. 常见问题:

a. 第一次页面加载会触发哪几个钩子?
b. vue中created与mounted区别以及各生命周期够级函数的特点和应用?

7. 组件通信

关键词:自定义事件、prop(camelCase)、单向数据流

分类:

  • 父子通信:props/$emit、$parent / $children、ref、provide/inject API、$attrs/$listeners
  • 兄弟通信:Bus($on/$emit);Vuex
  • 跨级通信:Bus($on/$emit);Vuex;provide / inject API、$attrs/$listeners

a. 父传子:props对象

  • 特点:父组件通过 props 向下传递数据给子组件。注:组件中的数据共有三种形式:data、props、computed。
//App.vue父组件
<template>
  <div id="app">
    <users v-bind:users="users"></users>
    // 前者自定义事件名便于子组件调用,后者要传递数据(简单类型)
    <blog-post v-bind="post"></blog-post>
    // 传入一个对象的所有 property
  </div>
</template>
<script>
import Users from "./components/Users"
export default {
    
    
  name: 'App',
  data(){
    
    
    return{
    
    
      users:["Henry","Bucky","Emily"]
    }
  },
  components:{
    
    
    "users":Users
  }
}

//users子组件
<template>
  <div class="hello">
    <ul>
      <li v-for="user in users">{
    
    {
    
    user}}</li>
      //遍历传递过来的值,然后呈现到页面
    </ul>
  </div>
</template>
<script>
export default {
    
    
  name: 'HelloWorld',
  props:{
    
    
    users:{
    
      //这个就是父组件中子标签自定义事件名
      type:Array,
// String/Number/Boolean/Array/Object/Date/Function/Symbol
      // 数字/字符串等简单数据类型默认值
      default: 100// 对象或数组默认值必须从一个工厂函数获取
      default: function () {
    
    
        return {
    
     message: 'hello' }
      },
      // 自定义验证函数
      propF: {
    
    
        validator: function (value) {
    
    
          // 这个值必须匹配下列字符串中的一个
          return ['success', 'warning', 'danger'].indexOf(value) !== -1
      },
      required:true
    }
  }
}
</script>

b. 子传父:通过事件形式

  • 特点:子组件通过 $emit()给父组件发送消息,父组件通过v-on绑定事件接收数据。
// 子组件
<template>
  <header>
    <h1 @click="changeTitle">{
    
    {
    
    title}}</h1>//绑定一个点击事件
  </header>
</template>
<script>
export default {
    
    
  name: 'app-header',
  data() {
    
    
    return {
    
    
      title:"Vue.js Demo"
    }
  },
  methods:{
    
    
    changeTitle() {
    
    
      // 自定义事件  传递值 “子向父组件传值”
      // this.$emit("事件名","数据");
      this.$emit("titleChanged","子向父组件传值");
    }
  }
}
</script>

// 父组件
<template>
  <div id="app">
    //与子组件titleChanged自定义事件保持一致
    <app-header v-on:titleChanged="updateTitle" ></app-header>
    // updateTitle($event)接受传递过来的文字
    <h2>{
    
    {
    
    title}}</h2>
  </div>
</template>
<script>
import Header from "./components/Header"
export default {
    
    
  name: 'App',
  data(){
    
    
    return{
    
    
      title:"传递的是一个值"
    }
  },
  methods:{
    
    
    updateTitle(e){
    
       //声明这个函数
      this.title = e;
    }
  },
  components:{
    
    
   "app-header":Header,
  }
}
</script>

c. 父子、兄弟、跨级:eventBus.js ($emit/$on)

  • 特点:这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来(emit)触发事件和(on)监听事件,巧妙而轻量地实现了任何组件间的通信。
var Event=new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {
    
    });

<div id="itany">
	<my-a></my-a>
	<my-b></my-b>
	<my-c></my-c>
</div>

<template id="a">
  <div>
    <h3>A组件:{
    
    {
    
    name}}</h3>
    <button @click="send">将数据发送给C组件</button>
  </div>
</template>

<template id="b">
  <div>
    <h3>B组件:{
    
    {
    
    age}}</h3>
    <button @click="send">将数组发送给C组件</button>
  </div>
</template>

<template id="c">
  <div>
    <h3>C组件:{
    
    {
    
    name}}{
    
    {
    
    age}}</h3>
  </div>
</template>
<script>
var Event = new Vue();//定义一个空的Vue实例
var A = {
    
    
	template: '#a',
	data() {
    
    
	  return {
    
    
	    name: 'tom'
	  }
	},
	methods: {
    
    
	  send() {
    
    
	    Event.$emit('data-a', this.name);
	  }
	}
}
var B = {
    
    
	template: '#b',
	data() {
    
    
	  return {
    
    
	    age: 20
	  }
	},
	methods: {
    
    
	  send() {
    
    
	    Event.$emit('data-b', this.age);
	  }
	}
}
var C = {
    
    
	template: '#c',
	data() {
    
    
	  return {
    
    
	    name: '',
	    age: ""
	  }
	},
	mounted() {
    
    //在模板编译完成后执行
	 Event.$on('data-a',name => {
    
    
	     this.name = name;
//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
	 })
	 Event.$on('data-b',age => {
    
    
	     this.age = age;
	 })
	}
}
var vm = new Vue({
    
    
	el: '#itany',
	components: {
    
    
	  'my-a': A,
	  'my-b': B,
	  'my-c': C
	}
});	
</script>

e. vuex

  • **特点:**vuex 是 vue 的状态管理器,存储的数据是响应式的。只需要把共享的值放到vuex中,其他需要的组件直接获取使用即可。
  • 功能:传递数据+中间数据处理

f. 跨级组件(多级组件嵌套)通讯$attrs/$listeners

  • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=“$attrs” 传入内部组件。通常配合 inheritAttrs 选项一起使用。
  • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=“$listeners” 传入内部组件

g. provide/inject父传子孙

Vue2.2.0新增API,这对选项需要一起使用,**以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。

provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的----vue官方文档 所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 浪里行舟。

// A.vue
export default {
    
    
  provide: {
    
    
    name: '浪里行舟'
  }}
// B.vue
export default {
    
    
  inject: ['name'],
  mounted () {
    
    
    console.log(this.name);  // 浪里行舟
  }
}

// 非响应式
provide() {
    
    
  return {
    
    
    theme: {
    
    
      color: this.color //这种方式绑定的数据并不是可响应的
    } // 即A组件的color变化后,组件D、E、F不会跟着变
  }
}

//方法一:提供祖先组件的实例
 provide() {
    
    
    return {
    
    
      theme: this
    };
  },

// 方法二:使用2.6最新API Vue.observable 优化响应式 provide
provide() {
    
    
  this.theme = Vue.observable({
    
    
    color: "blue"
  });
  return {
    
    
    theme: this.theme
  };
},

// 响应式
Vue.observable(object)
让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。
返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。

Vue.defineReactive()

h. $parent / $children与 ref父子通信

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例(非响应式)
  • $parent / $children:访问父 / 子实例(非响应式),访问组件的应急方法
    缺点:无法在跨级或兄弟间通信
// component-a 子组件
export default {
    
    
  data () {
    
    
    return {
    
    
      title: 'Vue.js'
    }
  },
  methods: {
    
    
    sayHello () {
    
    
      window.alert('Hello');
    }
  }
}

// 父组件
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    
    
    mounted () {
    
    
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 弹窗
    }
  }
</script>

解决单项数据流:

方法一 :使用sync修饰符,它会被扩展为一个自动更新父组件属性的 v-on 监听器

//渲染时需要:要更改的props数据.sync
<son :sname.sync="name"> </son>     

updatedata:function () {
    
    
// 以this.$emit("update:你要更改的props数据",改变后的值)
// 这种方法修改数据,渲染时需要:要更改的props数据.sync
  this.$emit('update:sname','Tom')
}

方法二:可以将父组件中的数据包装成对象或数组,然后在子组件中修改对象的属性

<template class="thisfather">
    <div>
        <p>这是父组件的数据,名字:{
    
    {
    
    user.name}}</p>
        <son :suser="user"> </son> //传递json对象
    </div>
</template>

<script>
// import Son from './son.vue'
export default {
    
    
  components:{
    
    
    son: Son
  }
  data(){
    
    
    return {
    
    
// 将需要改变的数据放到json数组里,然后以json对象的方式进行传递数据,
// 就可以直接进行双向修改
      user:{
    
       
          name: 'Jack',
       }
    }
  }
}
</script>
son.vue
<template class="thisson">
    <div>
        <p>这是子组件的数据,名字:{
    
    {
    
    suser.name}}</p>
        <button @click="updatedata">更新数据</button>
    </div>
</template>

<script>
export default {
    
    
  props:['suser'],
  data(){
    
    
    return {
    
    }
  },
  methods:{
    
    
    updatedata:function () {
    
    
      //直接进行双向修改   这个方法最为常用
      this.suser.name='Tom'  
    }
  }
}
</script>

!!! 我们是不允许直接修改父组件传过来的数据或对象的,而这种方法更改的是对象中的属性,因为对象是引用类型,指向同一内存空间,所以可以实现此效果。推荐使用该方式

8. ref(引用)/attrs

ref 对象

  1. 定义:被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  2. 重要说明:
    1. 因为 ref 本身是作为渲染结果被创建的,在初始渲染时不能被访问它们 - 不存在
    2. $refs 不是响应式的,不应该试图用它在模板中做数据绑定
  3. 三种用法:
    1. ref 加在普通的元素上,用this.$ref.name 获取到的是dom元素
    2. ref 加在子组件上,用this.$ref.name 获取到的是组件实例,可以使用组件的所有方法。
    3. 如何利用 v-for 和 ref 获取一组数组或者dom 节点
<div ref="name"></div>
<component-a ref="name"></component-a>
<component-b v-for='item in list' ref="name" :key='item.id'></component-b>

attrs 对象
父子之间值传递,我们一般用的是在属性里设置,采用:prop="prop"的格式

在子级的props定义接受,然后可以直接使用,若props里没有定义,那么可以使用this.$attrs里获取没有定义的值(非响应式)

  • 事件绑定,在props中可以用 事件名来接收
  • v-model也是个特殊的属性,在props中可以用 value来接收
还有子孙
传递:v-bind="$attrs"
获取:this.$attrs

9. solt 插槽(占位)

  1. 定义:槽用于决定将所携带的内容,插入到指定的某个位置,从而使模板分块,具有模块化的特质和更大的重用性。插槽显不显示、怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制
  2. 作用:正常情况下,< Child>< span style=”color:red;”>hello world</ span></ Child>在组件标签Child中的span标签会被组件模板template内容替换掉,当想让组件标签Child中内容传递给组件时需要使用slot插槽;
  3. 分类
    1. 单个插槽| 默认插槽| 匿名插槽
// 父组件
<template>
    <div class="father">
        <h3>这里是父组件</h3>
        <child>
           <span style=”color:red;>hello world</span>
        </child>
    </div>
</template>
// 子组件
<template>
    <div class="child">
        <h3>这里是子组件</h3>
        <slot></slot>
        <slot style=”color:blue;>这是在slot上添加了样式</slot>
        <slot name=”mySlot”>这是拥有命名的slot的默认内容</slot>
    </div>
</template>
// 输出:两个红色的hello world,以及一个使用slot的默认内容

//注意:在slot标签添加样式无效。拥有命名的插槽不能被不含slot属性的标签内容替换,
// 会显示slot的默认值(具名slot具有对应性);
  1. 具名插槽
// 父组件
<Child>
  <span slot="header">hello world</span>
  <span slot="main">hello world</span>
  <span slot="footer">hello world</span>
  <span slot="other">{
    
    {
    
    otherData}}</span>
</Child>

// 子组件
<template>
  <div>
    <slot  name=”header”>这是拥有命名的slot的默认内容</slot>
    <slot  name=”main”>这是拥有命名的slot的默认内容</slot>
    <slot  name=”footer”>这是拥有命名的slot的默认内容</slot>
    <slot  name=”other”>这是拥有命名的slot的默认内容</slot>
  </div>
</template>
  1. 作用域插槽 | 带数据的插槽

定义:使用时候子组件标签中要有<template slot-scope=”scopeName”>标签,再通过scopeName.childProp就可以调用子组件模板中的childProp绑定的数据,所以作用域插槽是一种子传父传参的方式,解决了普通slot在parent中无法访问child数据的去问题;

// 父组件
<child>
  <template slot-scope="user">
    <ul>
      <li v-for="item in user.data">{
    
    {
    
    item}}</li>
    </ul>
  </template>
</child>

// 子组件
<template>
  <div class="child">
 
    <h3>这里是子组件</h3>
    // 作用域插槽
    <slot :data="data"></slot>
  </div>
</template>
 
 export default {
    
    
    data: function(){
    
    
      return {
    
    
        data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
      }
    }
}

10. mixins 混入

mixins是一种分发 Vue 组件中可复用功能的非常灵活的方式。混合对象可以包含任意组件选项。当组件使用混合对象时,所有混合对象的选项将被混入该组件本身的选项。

而mixins引入组件之后,则是将组件内部的内容如data等方法、method等属性与父组件相应内容进行合并。相当于在引入后,父组件的各种属性方法都被扩充了。

特点:

  • vuex:用来做状态管理的,里面定义的变量在每个组件中均可以使用和修改,在任一组件中修改此变量的值之后,其他组件中此变量的值也会随之修改。
  • Mixins:可以定义共用的变量,在每个组件中使用,引入组件中之后,各个变量是相互独立的,值的修改在组件中不会相互影响。
    用法:
// myMixins.js
export const myMixins = {
    
    
  components:{
    
    },
  data() {
    
    
    return {
    
    
      age: 18
    }
  },
  created() {
    
    
    console.log('xxx from mixins')
  }
}

// 组件中
import {
    
     myMixins } from "@/mixins/myMixins.js";
export default {
    
    
  mixins: [myMixins],
  data() {
    
    
    return {
    
    }
  },
  created() {
    
    
    console.log('age1 from template =', this.age)
  }
}

问题:属性合并冲突

可点击vue中对mixins的理解和使用的介绍作为参考

11. keep-alive 动态组件

  • < keep-alive >是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
  • < keep-alive > 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

三、vue-router

前端路由是在保证只有一个HTML页面的情况下,通过对每个视图展示形式匹配一个特殊的url来实现所谓的切换效果。不会重新向服务端发送请求,也不会跳转页面。无论是刷新、前进、还是后退,都可以通过特殊url实现。

1. router和route的区别

  • router为VueRouter实例,想要导航到不同URL,则使用this.$router.push方法
  • route当前router跳转对象。里面可以获取name, path, params, query

2. vue-router 路由对象属性

属性:path、params、query、meta、matched

$route.path
类型: string
字符串,对应当前路由的路径,总是解析为绝对路径,如 /foo/bar。

$route.params
类型: Object
一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象

$route.query
类型: Object
一个key/value 对象,表示 URL 查询参数。
例如,对于路径/foo?user=1,则有 $route.query.user == 1,
如果没有查询参数,则是个空对象。

$route.meta
类型: Object
一个key/value 对象,表示路由元信息
//一级路由 
    {
    
    
      path: '/foo',
      component: Foo,
      children: [
        {
    
    
          path: 'bar',
          component: Bar,
          // a meta field
          meta: {
    
     requiresAuth: true ,keepAlive:true}
          //1.权限 2.内存缓存,单页面切换
        }
      ]
    }

$route.matched
类型:Array
一个数组,包含当前路由的所有嵌套路径片段的路由记录 。
路由记录就是 routes 配置数组中的对象副本 (还有在 children 数组)// 路由元信息 .meta $route.matched 搭配路由守卫 进行验证
router.beforeEach((to, from, next) => {
    
    
  if (to.matched.some(record => record.meta.requiresAuth)) {
    
    
    // this route requires auth, check if logged in
    // if not, redirect to login page.
    if (!auth.loggedIn()) {
    
    
      next({
    
    
        path: '/login',
        query: {
    
     redirect: to.fullPath }
      })
    } else {
    
    
      next()
    }
  } else {
    
    
    next() // 确保一定要调用 next()
  }
})

3. vue-router 路由跳转

  • 声明式(标签跳转)
<router-link :to="{name:'home'}" tag='li'></router-link>
<router-link :to="{path:'/home'}" tag='li'></router-link>
  • 编程式( js跳转)
相当于点击路由链接(可以返回到当前路由界面) --> 队列的方式(先进先出)
this.$router.push(path) 
  path参数:'/home' || {
    
    name:'home'} || {
    
    path:'/home'}

用新路由替换当前路由(不可以返回到当前路由界面) --> 栈的方式(先进后出)
this.$router.replace(path)
this.$router.back()
this.$router.go(-1 || 1)

4. vue-router 路由传参(动态路由)

  1. 定义:能传递参数的路由模式,由于可以传递参数-使用“动态路径参数”(dynamic segment) 来,所以其对应的路由数量是不确定的
  2. 种类

(1) params 方式

  • 配置路由格式:/router/:id
  • 传递的方式:在path后面跟上对应的值
  • 传递后形成的路径:/router/123

路由配置

// 1-params第一种传参路由配置
{
    
    
   path: '/user/:userid',
   component: User,
},

// 2-params第二种(name传参)或 query方式的路由配置
{
    
    
   path: 'homeDetails/:id',//params传参
   name:'homeDetails'
   component: () =>import ('@/views/home/homeDetails.vue'),
   //子路由的绝对路径
  },

// 重定向
const routes = [{
    
     path: '/home', redirect: '/' }]
const routes = [{
    
     path: '/home', redirect: {
    
     name: 'homepage' } }]
const routes = [
  {
    
    
    // /search/screens -> /search?q=screens
    path: '/search/:searchText',
    redirect: to => {
    
    
      // 方法接收目标路由作为参数
      // return 重定向的字符串路径/路径对象
      return {
    
     path: '/search', query: {
    
     q: to.params.searchText } }
    },
  },
  {
    
    
    path: '/search',
    // ...
  },
]

// 别名
const routes = [{
    
     path: '/', component: Homepage, alias: '/home' }]
const routes = [
  {
    
    
    path: '/users/:id',
    component: UsersByIdLayout,
    children: [
      // 为这 3 个 URL 呈现 UserDetails
      // - /users/24
      // - /users/24/profile
      // - /24
      {
    
     path: 'profile', component: UserDetails, alias: ['/:id', ''] },
    ],
  },
]

路由跳转

// 1-router-link 申明式路由跳转传参
<router-link to="/homeDetails/12345"></router-link>
<router-link to="{name:'homeDetails',params:{id:12345}"></router-link>

// 2-this.$router.push 编程式路由跳转传参
this.$router.push({
    
    path:`/homeDetails/${
      
      id}`}) 
//params方式传参第一种方式
this.$router.push({
    
    name:'homeDetails',params:{
    
    id:id}}) 
//params方式传参第二种方式

获取参数

// 子组件使用this.$route.params.id来接收路由参数

// 获取方式
1-导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中...”之类的指示。
2-导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

(2) query 方式

  • 配置路由格式:/router,也就是普通配置
  • 传递的方式:对象中使用query的key作为传递方式
  • 传递后形成的路径:/route?id=123

路由配置

{
    
    
  path: 'homeDetails/',//query传参
  name:'homeDetails'
  component: () =>import ('@/views/home/homeDetails.vue'),
  //子路由的绝对路径
},

路由跳转

// 1-router-link 申明式路由跳转传参
<router-link to="/homeDetails?id=12345"></router-link>
<router-link to="{path:'/homeDetails',query:{id:12345}"></router-link>
<router-link to="{name:'homeDetails',query:{id:12345}"></router-link>

// 2-this.$router.push 编程式路由跳转传参
this.$router.push({
    
    path:`/homeDetails`, query:{
    
    id:id}})
// query方式传参ils/108197898

获取参数

// 子组件使用this.$route.query.id来接收路由参数

4. vue-router 路由匹配

// 1-在参数中自定义正则
const routes = [
  // /:orderId -> 仅匹配数字
  {
    
     path: '/:orderId(\\d+)' },
  // /:productName -> 匹配其他任何内容
  {
    
     path: '/:productName' },
]

// 2-可重复的参数
const routes = [
  // + 修饰符(1 个或 多 个)
  // /:chapters ->  匹配 /one, /one/two, /one/two/three, 等
  {
    
     path: '/:chapters+' },
  
  // * 修饰符(0 个或 多 个)
  // /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
  {
    
     path: '/:chapters*' },
  
  // ? 修饰符(0 个或 1 个)
  // 匹配 /users 和 /users/posva
  {
    
     path: '/users/:userId?' },
  // 匹配 /users 和 /users/42
  {
    
     path: '/users/:userId(\\d+)?' },
]

// 给定 { path: '/:chapters*', name: 'chapters' },
router.resolve({
    
     name: 'chapters', params: {
    
     chapters: [] } }).href
// 产生 /
router.resolve({
    
     name: 'chapters', params: {
    
     chapters: ['a', 'b'] } }).href
// 产生 /a/b
// 给定 { path: '/:chapters+', name: 'chapters' },
router.resolve({
    
     name: 'chapters', params: {
    
     chapters: [] } }).href
// 抛出错误,因为 `chapters` 为空 

// 3-Sensitive 与 strict 路由配置
const router = createRouter({
    
    
  history: createWebHistory(),
  routes: [
    // 将匹配 /users/posva 而非:
    // - /users/posva/ 当 strict: true
    // - /Users/posva 当 sensitive: true
    {
    
     path: '/users/:id', sensitive: true },
    // 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/
    {
    
     path: '/users/:id?' },
  ]
  strict: true, // applies to all routes
})

5. vue-router 路由模式

改变路径的方式有两种:(都不重新刷新页面)

  • URL 的 hash
  • HTML5 的 history
hash history
url显示 有#,很Low 无#,好看
回车刷新 可以加载到hash值对应页面 一般就是404掉了
支持版本 支持低版本浏览器和IE浏览器 HTML5新推出的API

默认情况下,路径改变使用URL的 hash,如果希望使用HTML5的history模式,进行如下的配置:

// 4-创建VueRouter实例对象

const router = new VueRouter({
  // 3-配置路由和组件之间的应用关系
  routes,
  // mode:'hash'//哈希模式   location.href
  mode: 'history' //历史记录   history.pushState
});

Hash 模式:#—hash符号,中文名哈希符或锚点。路由的哈希模式是利用window的监听onhashchange事件,当url中的哈希值(#后面的值)发生变化时,前端可以监听并做出响应。

History 模式:History API 提供了pushState()replaceState()方法来增加或替换历史记录。可以将url替换并且不刷新页面。但需要后台配置,否则输入的除首页外都为404(当然系统内跳转可以)。问题:不怕前进,不怕后退,就怕刷新。

  • pushState方法不会触发页面刷新,只是导致history对象发生变化,地址栏会有反应。

history 对象通常具有以下属性和方法:

  • length: number 浏览历史堆栈中的条目数
  • action: string 路由跳转到当前页面执行的动作,分为 PUSH, REPLACE, POP
  • location: object 当前访问地址信息组成的对象,具有如下属性:
  • pathname: string URL路径
  • search: string URL中的查询字符串
  • hash: string URL的 hash 片段
  • state: string 例如执行 push(path, state) 操作时,location 的 state 将被提供到堆栈信息里,state 只有在 browser 和 memory history 有效。
  • push(path, [state]) 在历史堆栈信息里加入一个新条目。
  • replace(path, [state]) 在历史堆栈信息里替换掉当前的条目
  • go(n) 将 history 堆栈中的指针向前移动 n。
  • goBack() 等同于 go(-1)
  • goForward 等同于 go(1)
  • block(prompt) 阻止跳转
  • history 对象是可变的,因为建议从 的 prop 里来获取 location,而不是从 history.location 直接获取。

6. vue-router 路由元信息

const routes = [
  {
    
    
    path: '/posts',
    component: PostsLayout,
    children: [
      {
    
    
        path: 'new',
        component: PostsNew,
        // 只有经过身份验证的用户才能创建帖子
        meta: {
    
     requiresAuth: true }
      },
      {
    
    
        path: ':id',
        component: PostsDetail
        // 任何人都可以阅读文章
        meta: {
    
     requiresAuth: false }
      }
    ]
  }
]

router.beforeEach((to, from) => {
    
    
  // 而不是去检查每条路由记录
  // to.matched.some(record => record.meta.requiresAuth)
  if (to.meta.requiresAuth && !auth.isLoggedIn()) {
    
    
    // 此路由需要授权,请检查是否已登录
    // 如果没有,则重定向到登录页面
    return {
    
    
      path: '/login',
      // 保存我们所在的位置,以便以后再来
      query: {
    
     redirect: to.fullPath },
    }
  }
})

7. vue-router 路由守卫(钩子函数)

钩子函数种类有:全局守卫、路由守卫、组件守卫

  1. 全局路由
    全局导航钩子主要有三种钩子:前置守卫(beforeEach)、beforeResolve、后置钩子(afterEach)

  2. 路由独享的钩子
    单个路由独享的导航钩子,它是在路由配置上直接进行定义的 beforeEnter

routes: [
         {
    
    
            path: '/file',
            component: File,
            beforeEnter: (to, from ,next) => {
    
     
             //do something
            }
         }
        ]

// 钩子函数参数:
// to:router即将进入的路由对象
// from:当前导航即将离开的路由
// next:Function,进行管道中的一个钩子,
// 如果执行完了,则导航的状态就是 confirmed (确认的);否则为false,终止导航。
  1. 组件内的导航钩子
    组件内的导航钩子主要有这三种:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。他们是直接在路由组件内部直接进行定义的。

ps:详细知识点可以点击路由导航守卫查看;

完整的导航解析流程:

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

8. vue-router 滚动行为

const router = createRouter({
    
    
  history: createWebHashHistory(),
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    
    
    // 1-return 期望滚动到哪个的位置
    return {
    
     
      top: 0  // 始终滚动到顶部
    }
    
    // 2-始终在元素 #main 上方滚动 10px
    return {
    
    
      // 也可以这么写
      // el: document.getElementById('main'),
      el: '#main',
      top: -10, // 该元素的相对偏移量
    }
    // 3-在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:
    if (savedPosition) {
    
    
      return savedPosition
    } else {
    
    
      return {
    
     top: 0 }
    }
    
    // 4-滚动到锚点
    if (to.hash) {
    
    
      return {
    
    
        el: to.hash,
        behavior: 'smooth' // 滚动更流畅
      }
    }
    // 5-延迟滚动
    return new Promise((resolve, reject) => {
    
    
      setTimeout(() => {
    
    
        resolve({
    
     left: 0, top: 0 })
      }, 500)
    })
  }
})

9. vue-router 路由懒加载

路由懒加载的含义:把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件。

// 将
// import UserDetails from './views/UserDetails'
// 替换成
const UserDetails = () => import('./views/UserDetails')
const router = createRouter({
    
    
  // ...
  routes: [{
    
     path: '/users/:id', component: UserDetails }],
})

四、Vuex

1. Vuex是什么?怎么使用?

定义:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
请添加图片描述

作用:

  • Vuex是实现组件全局状态(数据)管理的一种机制,可以方便实现组件数据之间的共享
  • Vuex集中管理共享的数据,易于开发和后期维护;
  • 能够高效的实现组件之间的数据共享,提高开发效率;
  • 存储在Vuex的数据是响应式的,能够实时保持页面和数据的同步;
    Vuex重要核心属性包括:state, mutations, action, getters, modules.
  1. state => 基本数据/状态(数据源存放地),不可直接修改
// 1-state 状态或数据
computed: {
    
    
    count () {
    
    
      // store模式
      return store.state.count
      // vuex模式
      return this.$store.state.count
    },  
  },
 
// 2-辅助函数 mapState()
computed: mapState({
    
    
    // 1-箭头函数可使代码更简练
    count: state => state.count,
    // 传字符串参数 'count' 等同于 `state => state.count`
    
    // 2-映射 this.count 为 store.state.count
    'count', // 映射的计算属性的名称与 state 的子节点名称相同时
    
    countAlias: 'count',
    
    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
    
    
      return state.count + this.localCount
    }
  })

//  3-对象展开运算符 ...mapState()
 computed: {
    
    
  localComputed () {
    
     /* ... */ },
  // 使用对象展开运算符将此对象混入到外部对象中
  ...mapState({
    
    
    // ...
  })
}
  1. getters => 从基本数据派生出来的数据(数据简单的过滤操作)
getters: {
    
    
    doneTodos (state) {
    
    
      return state.todos.filter(todo => todo.done)
    }
}
// 访问
store.getters.doneTodos

getters: {
    
    
  // ...
  doneTodosCount (state, getters) {
    
    
    return getters.doneTodos.length
  }
}
// 访问
store.getters.doneTodosCount

// 通过方法访问(可传值)
getters: {
    
    
  // ...
  getTodoById: (state) => (id) => {
    
    
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2)

// 辅助函数 mapGetters
computed: {
    
    
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
}
  1. mutations => 定义动态更改store中的状态或数据的唯一方法,同步
    a. 特点:Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。
    b. mutation 必须是**同步函数,**任何在回调函数中进行的状态的改变都是不可追踪的。
// 定义更改状态的唯一方法
mutations: {
    
    
    increment (state) {
    
    
      // 变更状态
      state.count++
    }
  }
  
// 提交方法 commit()
store.commit('increment')

// 提交载荷(Payload)-对象 :传入额外的参数
mutations: {
    
    
  increment (state, payload) {
    
    
    state.count += payload.amount
  }
}
// 提交方法 commit()
store.commit('increment', {
    
    
  amount: 10
})

// 对象风格的提交
store.commit({
    
    
  type: 'increment', // 事件类型
  amount: 10
})

// 使用常量替代 mutation 事件类型
mutations: {
    
    
    // 我们可以使用 ES2015 风格的计算属性命名功能
    // 来使用一个常量作为函数名
    [SOME_MUTATION] (state) {
    
    
      // 修改 state
    }
  }
  
// 在组件中提交 Mutation 对象展开运算符 ...mapMutations
methods: {
    
    
    ...mapMutations([
      'increment', 
// 将 `this.increment()` 映射为 `this.$store.commit('increment')`
      // `mapMutations` 也支持载荷:
      'incrementBy' 
// 将 `this.incrementBy(amount)` 映射为 
// `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
    
    
      add: 'increment' 
// 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
  1. actions => 像一个装饰器,包裹mutations,使之可以异步。
    Action 类似于 mutation,不同在于:
  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。

const store = createStore({
    
    
  state: {
    
    
    count: 0
  },
  mutations: {
    
    
    increment (state) {
    
    
      state.count++
    }
  },
  actions: {
    
    
    increment (context) {
    
    
      context.commit('increment')
    }
  }
})

// 分发 Action
store.dispatch('increment')

// 异步操作
actions: {
    
    
  incrementAsync ({
     
      commit }) {
    
    
    setTimeout(() => {
    
    
      commit('increment')
    }, 1000)
  }
}

// 以载荷形式分发
store.dispatch('incrementAsync', {
    
    
  amount: 10
})
// 以对象形式分发
store.dispatch({
    
    
  type: 'incrementAsync',
  amount: 10
})

// 在组件中分发 Action
methods: {
    
    
    ...mapActions([
      'increment', 
// 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
      // `mapActions` 也支持载荷:
      'incrementBy' 
// 将 `this.incrementBy(amount)` 
// 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
    
    
      add: 'increment' 
// 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
  1. modules => 模块化Vuex
  2. commit => 触发 mutations中的方法 操作数据状态变更
  3. dispatch => 分发 action

ps: 详细使用和对其各属性的理解可以参考以下文章!

  • 认识vuex和使用
  • 核心概念State和Mutation的理解和使用
  • 核心概念Action和Getter的理解和使用

2. 如何在 Vue 组件中展示状态?

由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态

3. 什么情况下使用 Vuex?

如果应用够简单,最好不要使用 Vuex,一个简单的 store 模式即可;

需要构建一个中大型单页应用时,使用Vuex能更好地在组件外部管理状态;

4. Vuex和单纯的全局对象有什么区别?

Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

5. 为什么 Vuex 的 mutation 中不能做异步操作?

每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

五、Vue3.0

1. Vue3有了解过吗?能说说跟Vue2的区别吗?

Vue3的体积更小。在脚手架打包的时候,如果vue3中没有使用到的语法,是不会最终进行合并打包的,所以vue3的体积会更小。 tree-shaking(摇树优化)。

2、Vue3的性能更好。Vue3的diff算法比2的diff算法更好。

  • Vue2是采用 Object.defineProperty() 数据监听的,只能监听对象的一层,如果要监听多层,就得递归对每一层进行监听;
  • Vue3是采用 proxy 数据监听的,可以直接对对象整体进行监听,不需要递归。

3、Vue的源码不同

Vue的源码 Vue2使用JS去写的 ;Vue3使用TS去写的,所以Vue3 + TypeScript开发更好。

4、编写代码的风格不同。

① Vue2是 选项式风格( option api )

new Vue({
    
    
    data,
    methods,
    computed,
    watch
    ....
})

② Vue3不仅支持选项式风格(option api ), 还支持 组合式风格(composition api)。

//Vue3中要求,不管是跟组件还是普通组件,data必须是一个函数
    //Vue3脚手架中的tmeplate可以有多个跟标签
    //Vue2 -> vetur插件
    //Vue3 -> Vue Language Features (Volar)插件

vue3 -> vue-router4 -> 没有大的区别

  1. 格式上做了微调
  2. 提供了两个新的 use函数 useRoute -> this.$route useRouter -> this.$router

vue3 -> vuex4 -> 没有大的区别

  1. 格式上做了微调
  2. use函数 useStore

2. Vue 3.0 所采用的 Composition Api 与 Vue 2.x使用的Options Api 有什么区别?

  1. Options Api
    • 包含一个描述组件选项(data、methods、props等)的对象 options;
    • API开发复杂组件,同一个功能逻辑的代码被拆分到不同选项 ;
    • 使用mixin重用公用代码,也有问题:命名冲突,数据来源不清晰;
  2. Composition Api
    • vue3 新增的一组 api,它是基于函数的 api,可以更灵活的组织组件的逻辑。
    • 解决options api在大型项目中,options api不好拆分和重用的问题。

六、其他

1. 命名规则

**格式:**kebab-case:{ {content}}-{ {a}}-{ {b}}-{ {c}}

    * 文件名:kebab-case || PascalCase
        * 基础组件名:特定的前缀开头,比如 Base、App 或 V
        * 单例组件名:特定的前缀开头,比如 The
        * 完整单词的组件名:
        * 组件名中的单词顺序:组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。比如 SearchButtonClear.vue
        * 自闭合组件:
            * <!-- 在 DOM 模板中 -->:<my-component/>
            * <!-- 在单文件组件、字符串模板和 JS/JSX 中 --> :<MyComponent/>
    * 组件名:
        * <!-- 在单文件组件、字符串模板和 JSX 中 --> :PascalCase
        * <!-- 在 DOM 模板中 -->:kebab-case
    * 事件名:camelCase
    * prop/数据名:
        * 申明:camelCase
        * 模板和 JSX :kebab-case
    * HTML attribute 值:始终带引号 (单引号或双引号)。

不同于组件( kebab-case || PascalCase)和 prop,事件名不存在任何自动化的大小写转换。

2. axios 是什么,其特点和常用语法

是什么?

  1. Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。前端最流行的 ajax 请求库,
  2. react/vue 官方都推荐使用 axios 发 ajax 请求

特点:

  1. 基于 promise 的异步 ajax 请求库,支持promise所有的API
  2. 浏览器端/node 端都可以使用,浏览器中创建XMLHttpRequests
  3. 支持请求/响应拦截器
  4. 支持请求取消
  5. 可以转换请求数据和响应数据,并对响应回来的内容自动转换成 JSON类型的数据
  6. 批量发送多个请求
  7. 安全性更高,客户端支持防御 XSRF,就是让你的每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到你cookie中得key的,这样,后台就可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。

常用语法:

  • axios(config): 通用/最本质的发任意类型请求的方式
  • axios(url[, config]): 可以只指定 url 发 get 请求
  • axios.request(config): 等同于 axios(config)
  • axios.get(url[, config]): 发 get 请求
  • axios.delete(url[, config]): 发 delete 请求
  • axios.post(url[, data, config]): 发 post 请求
  • axios.put(url[, data, config]): 发 put 请求
  • axios.defaults.xxx: 请求的默认全局配置
  • axios.interceptors.request.use(): 添加请求拦截器
  • axios.interceptors.response.use(): 添加响应拦截器
  • axios.create([config]): 创建一个新的 axios(它没有下面的功能)
  • axios.Cancel(): 用于创建取消请求的错误对象
  • axios.CancelToken(): 用于创建取消请求的 token 对象
  • axios.isCancel(): 是否是一个取消请求的错误
  • axios.all(promises): 用于批量执行多个异步请求
  • axios.spread(): 用来指定接收所有成功数据的回调函数的方法

3. 对SSR有了解吗,它主要解决什么问题?

Server-Side Rendering 我们称其为SSR,意为服务端渲染指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程;

解决了以下两个问题:

  • seo:搜索引擎优先爬取页面HTML结构,使用ssr时,服务端已经生成了和业务想关联的HTML,有利于seo
  • 首屏呈现渲染:用户无需等待页面所有js加载完成就可以看到页面视图(压力来到了服务器,所以需要权衡哪些用服务端渲染,哪些交给客户端)

缺点

  • 复杂度:整个项目的复杂度
  • 性能会受到影响
  • 服务器负载变大,相对于前后端分离务器只需要提供静态资源来说,服务器负载更大,所以要慎重使用

4. Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做?

具体详解查看Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做

5. Vue项目前端开发环境请求服务器接口跨域问题

对于vue-cli 2.x版本在config文件夹配置服务器代理;

对于vue-cli 3.x版本前端配置服务器代理在vue.config.js中设置服务器代理;如下图:

target对应的属性值为你准备向后端服务器发送请求的主机+端口,含义为:相当于把前端发送请求的主机+端口自动替换成挂载的主机和端口,这样前后端的主机端口都一一就不会存在跨域问题;

ws:表示WebSocket协议;

changeOrigin:true;表示是否改变原域名;这个一定要选择为true;

这样发送请求的时候就不会出现跨域问题了。

6. 做过哪些Vue的性能优化?

编码阶段

  • 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
  • v-if和v-for不能连用
  • 如果需要使用v-for给每项元素绑定事件时使用事件代理
  • SPA 页面采用keep-alive缓存组件
  • 在更多的情况下,使用v-if替代v-show
  • key保证唯一
  • 使用路由懒加载、异步组件
  • 防抖、节流
  • 第三方模块按需导入
  • 长列表滚动到可视区域动态加载
  • 图片懒加载

SEO优化

  • 服务端渲染SSR
  • 预渲染

打包优化

  • 压缩代码
  • Tree Shaking/Scope Hoisting
  • 使用cdn加载第三方模块
  • 多线程打包happypack
  • splitChunks抽离公共文件
  • sourceMap优化

猜你喜欢

转载自blog.csdn.net/qq_43000315/article/details/125771157