Vue 常见面试题汇总(这些技巧你真的都掌握了吗?呕心沥血2w字整理)

目录

前言

Vue 的优缺点

SPA的理解

MVVM的理解

单向数据流的理解

响应式原理

手写观察者模式

手写发布订阅模式

组件中的data为什么是一个函数

生命周期

父组件与子组件生命周期钩子执行顺序

父组件监听子组件生命周期钩子的方式

组件通信方式

v-on监听多个方法

常用修饰符

class、style的动态绑定方式

v-show与v-if的区别

为什么v-if不能和v-for一起使用

computed与watch的区别与使用场景

slot插槽的理解与使用

Vue.$delete和delete的区别

Vue.$set如何解决对象新增属性不能响应的问题

Vue.$nextTick的原理

虚拟DOM的理解

Diff算法原理

key的作用

动态组件 和 异步组件

Vue.directive 有写过么,有哪些应用场景

Vue 过滤器

关于mixin的理解,有什么应用场景

介绍一下keep-alive

Vue-Router 配置 404 页面

Vue-Router 有哪几种导航守卫

Vue-Router 完整的导航解析流程

Vuex 的理解及使用

Vuex 刷新后数据丢失怎么办

Vuex 如何知道 State 是通过 Mutation 修改还是外部修改

Vue SSR 了解么

Vue2 与 Vue3 的区别 ?Vue3有哪些优化点?

Vue 性能优化


前言

本文汇总了vue常用知识点与常见面试题,附上本人对该vue知识介绍相关博客,适合收藏,经常回顾,也许每次阅读都会有进一步理解。

Vue 的优缺点

优点

1、创建单页面应用的轻量级Web应用框架

2、简单易用

3、双向数据绑定

4、组件化的思想

5、虚拟DOM

6、数据驱动视图

7、前后端分离

缺点

1、Vue在开发多页应时不够灵活,需要配置多入口

2、不支持IE8

SPA的理解

SPA是 Single-Page-Application 的缩写,翻译过来就是单页应用。在WEB页面初始化时一同加载Html、Javascript、Css。一旦页面加载完成,SPA不会因为用户操作而进行页面重新加载或跳转,取而代之的是利用路由机制实现Html内容的变换。

优点

1、良好的用户体验,内容更改无需重载页面

2、SPA相对服务端压力更小

3、前后端职责分离,架构清晰

缺点

1、由于前端渲染,搜索引擎不会解析JS,只能抓取首页未渲染的模板,不利于SEO

2、单页面应用,在加载页面的时候将JavaScript、CSS统一加载,所以首次加载耗时更多

3、单页面应用需在一个页面显示所有的内容,默认不支持浏览器的前进后退(前端路由机制解决了该窘境,Hash模式中Hash变化会被浏览器记录,History模式利用 H5 新增的pushStatereplaceState方法可改变浏览器历史记录栈)

MVVM的理解

MVVM是Model-View-ViewModel的缩写。Model 代表数据层,可定义修改数据、编写业务逻辑。View 代表视图层,负责将数据渲染成页面。ViewModel 负责监听数据层数据变化,控制视图层行为交互,简单讲,就是同步数据层和视图层的对象。ViewModel 通过双向绑定把 View 和 Model 层连接起来,且同步工作无需人为干涉,使开发人员只关注业务逻辑,无需频繁操作DOM,不需关注数据状态的同步问题。

具体细节参考文章

面试官:你了解MVVM框架吗?(Vue MVVM详细介绍,一看就会)_czjl6886的博客-CSDN博客随着前端的发展,MVVM思想越来越受到大家的欢迎,那么MVVM到底是什么呢?下面,我将简要介绍MVVM的思想,并从Vue的角度,分析具体代码中是怎么实现这种思想的。https://blog.csdn.net/czjl6886/article/details/121717212?spm=1001.2014.3001.5502

单向数据流的理解

我们经常说 Vue 的双向绑定,其实是在单向绑定的基础上给元素添加 input/change 事件,来动态修改视图。Vue 组件间传递数据仍然是单项的,即父组件传递到子组件。子组件内部可以定义依赖 props 中的值,但无权修改父组件传递的数据,这样做防止子组件意外变更父组件的状态,导致应用数据流向难以理解。

如果在子组件内部直接更改prop,会遇到警告处理。

以下为2种定义依赖props中的值

1、通过 data 定义属性并将 prop 作为初始值

<script>
export default {
  props: ['userName'],
  data() {
    return {
      name: this.userName
    }
  }
}
</script>

2、 用 computed 计算属性去定义依赖 prop 的值

<script>
export default {
  props: ['userName'],
  computed: {
    helloName() {
      return "hello" + this.userName
    }
  }
}
</sciprt>

响应式原理

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setterObject.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

data

响应式原理3个步骤:数据劫持、依赖收集、派发更新。

数据分为两类:对象、数组

对象

遍历对象,通过Object.defineProperty为每个属性添加 getter 和 setter,进行数据劫持。getter 函数用于在数据读取时进行依赖收集,在对应的 dep 中存储所有的 watcher;setter 则是数据更新后通知所有的 watcher 进行更新。

核心源码

function defineReactive(obj, key, val, shallow) {
  // 实例化一个 dep, 一个 key 对应一个 dep
  const dep = new Dep()
 
  // 获取属性描述符
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  // 通过递归的方式处理 val 为对象的情况,即处理嵌套对象
  let childOb = !shallow && observe(val)
  
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // 拦截obj.key,进行依赖收集
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      // Dep.target 是当前组件渲染的 watcher
      if (Dep.target) {
        // 将 dep 添加到 watcher 中
        dep.depend()
        if (childOb) {
          // 嵌套对象依赖收集
          childOb.dep.depend()
          // 响应式处理 value 值为数组的情况
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 获取旧值
      const value = getter ? getter.call(obj) : val
      // 判断新旧值是否一致
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }

      if (getter && !setter) return
      // 如果是新值,用新值替换旧值
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 新值做响应式处理
      childOb = !shallow && observe(newVal)
      // 当响应式数据更新,依赖通知更新
      dep.notify()
    }
  })
}

数组

用数组增强的方式,覆盖原属性上默认的数组方法,保证在新增或删除数据时,通过 dep 通知所有的 watcher 进行更新。

核心源码

const arrayProto = Array.prototype
// 基于数组原型对象创建一个新的对象
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  // 分别在 arrayMethods 对象上定义7个方法
  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)
    // 数据无论是新增还是删除都进行派发更新
    ob.dep.notify()
    return result
  })
})

手写观察者模式

let uid = 0
class Dep {
  constructor() {
    this.id = uid++
    // 存储所有的 watcher
    this.subs = []
  }
  addSub(sub) {
    this.subs.push(sub)
  }
  removeSub(sub) {
    if(this.subs.length) {
      const index = this.subs.indexOf(sub)
      if(index > -1) return this.subs.splice(index, 1)
    }
  }
  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}

class Watcher {
  constructor(name) {
    this.name = name
  }
  update() {
    console.log('更新')
  }
}

手写发布订阅模式

与观察者模式相似,区别在于发布者和订阅者是解耦的,由中间的调度中心去与发布者和订阅者通信。Vue响应式原理个人更倾向于发布订阅模式。其中 Observer 是发布者,Watcher 是订阅者,Dep 是调度中心。

class EventEmitter {
  constructor() {
    this.events = {}
  }
  on(type, cb) {
    if(!this.events[type]) this.events[type] = []
    this.events[type].push(cb)
  }
  emit(type, ...args) {
    if(this.events[type]) {
      this.events[type].forEach(cb => {
        cb(...args)
      })
    }
  }
  off(type, cb) {
    if(this.events[type]) {
      const index = this.events[type].indexOf(cb)
      if(index > -1) this.events[type].splice(index, 1)
    }
  }
}

组件中的data为什么是一个函数

数据以函数返回值形式定义,当每复用一次组件,就会返回一份新的data。即给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。对象在栈中存储的都是地址,函数的作用就是属性私有化,保证组件修改自身属性时不会影响其他复用组件。

生命周期

生命周期 描述
beforeCreate vue实例初始化后,数据观测(data observer)和事件配置之前。data、computed、watch、methods都无法访问。
created vue实例创建完成后立即调用 ,可访问 data、computed、watch、methods。未挂载 DOM,不能访问 、ref。
beforeMount 在 DOM 挂载开始之前调用。
mounted vue实例被挂载到 DOM。
beforeUpdate 数据更新之前调用,发生在虚拟 DOM 打补丁之前。
updated 数据更新之后调用。
beforeDestroy 实例销毁前调用。
destroyed 实例销毁后调用 。

调用异步请求可在createdbeforeMountmounted生命周期中调用,因为相关数据都已创建。在不涉及到DOM操作时,最好的选择是在created中调用。

具体细节参考文章

Vue 生命周期 详细介绍(面试必考,内附实例截图)_前端不释卷leo的博客-CSDN博客Vue的生命周期是不仅是面试必考点,理解它更能为我们在Vue项目开发带来促进作用。什么是Vue的生命周期?每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。简单理解就是Vue实例从创建到销毁的这么一个过程。如图(官网)所示:Vue生命周期函数(钩子)执行顺序从上图可知主要为八大生命周期钩子:beforhttps://blog.csdn.net/qq_41809113/article/details/121684566?spm=1001.2014.3001.5502

父组件与子组件生命周期钩子执行顺序

加载渲染过程

速记:父先创建,才能有子;子创建完成,父才完整。

顺序:父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

组件更新过程

顺序:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

销毁过程

顺序:父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

父组件监听子组件生命周期钩子的方式

以mounted为例

1、$emit

// 父组件
<template>
  <div class="parent">
    <!-- 监听子组件mounted事件 -->
    <Child @mounted="doSomething"/>
  </div>
</template>
<script>
export default {  
  methods: {
    doSomething() {
      console.log('父组件监听到子组件 mounted 钩子函数')
    }
  }
}
</script>

//子组件
<template>
  <div>
  </div>
</template>
<script>
export default {
  mounted() {
    console.log('触发mounted事件...')
    this.$emit("mounted")
  }
}
</script>

2、@hook

// 父组件
<template>
  <div class="parent">
    <Child @hook:mounted="doSomething"/>
  </div>
</template>
<script>
export default {  
  methods: {
    doSomething() {
      console.log('父组件监听到子组件 mounted 钩子函数')
    }
  }
}
</script>

//子组件
<template>
  <div>
  </div>
</template>
<script>
export default {
  mounted() {
    console.log('触发mounted事件...')
  }
}
</script>

组件通信方式

父子组件

1、props、$emit

2、$parent、$children

跨级组件

1、$attrs、$listeners

2、provide、inject

父子、跨级、兄弟

1、EventBus(事件总线)

2、Vuex(状态管理)

扩展

1、缓存

2、路由

具体细节参考文章

Vue组件间的通信方式(多种场景,通俗易懂,建议收藏)_前端不释卷leo的博客-CSDN博客_vue组件间通信以下是我在工作中用到过的vue组件之间的通信方式,不同的场景使用不同的方式,基本满足所有开发场景中的通信需求,话不多说直接开始,满满的干货,建议收藏。1、父子组件之间的通信父 >>> 子 (Props)一个组件里面引入另外一个组件,此时构成了一种“父子关系”,当前组件为“父”,引入的组件为“子”,如当前组件(父),在父组件中通过 “:message” 向子组件通信。<template> <div class="paren...https://blog.csdn.net/qq_41809113/article/details/120384336?spm=1001.2014.3001.5502

v-on监听多个方法

<button v-on="{mouseenter: onEnter, mouseleave: onLeave}">鼠标进来1</button>

常用修饰符

表单修饰符

1、lazy::失去焦点后同步信息

2、trim:自动过滤首尾空格

3、number:输入值转为数值类型

事件修饰符

1、stop:阻止冒泡(js中stopPropagation)

2、prevent:阻止默认行为(js中preventDefault)

3、self:仅绑定元素自身触发

4、once:只触发一次

class、style的动态绑定方式

对象方式

<template>
  <div :class="{ active: isActive }"></div>
  <div :style="{ fontSize: fontSize }">
</template>
<script>
export default {
  data() {
    return {
      isActive: true,    //如果为false,则不绑定"active"类
      fontSize: 30
    }
  }
}
</script>

数组方式

<template>
  <div :class="[activeClass]"></div>
  <div :style="[styleFontSize]">
</template>
<script>
export default {
  data() {
    return {
      activeClass: 'active',
      styleFontSize: {
        fontSize: '30px'
      }
    }
  }
}
</script>

v-show与v-if的区别

相同点

都是控制页面元素的显示与隐藏

不同点

1、v-show 控制的是元素的CSS(v-show="false"相当于display:none);v-if 是控制元素本身的添加或删除(即是否出现在DOM结构中)

2、v-show 由 false 变为 true 的时候不会触发组件的生命周期。v-if 由 false 变为 true 则会触发组件的beforeCreatecreatebeforeMountmounted钩子,由 true 变为 false 会触发组件的beforeDestorydestoryed方法

3、频繁切换时,使用v-show;不频繁切换、加快页面渲染、需要销毁元素使用v-if

为什么v-if不能和v-for一起使用

性能浪费,每次渲染都要先循环再进行条件判断,考虑用计算属性替代。

Vue2.x中v-forv-if更高的优先级。

Vue3.x中v-if 比 v-for 更高的优先级。

computed与watch的区别与使用场景

computed

计算属性,依赖其他属性值,且值具备缓存的特性。只有它依赖的属性值发生改变,下一次获取的值才会重新计算。

适用于数值计算,并且依赖于其他属性时。因为可以利用缓存特性,避免每次获取值,都需要重新计算。

watch

观察属性,监听属性值变动。每当属性值发生变化,都会执行相应的回调。

适用于数据变化时执行异步或开销比较大的操作。

具体细节参考文章

上手Vue:深度理解computed、watch及其区别_czjl6886的博客-CSDN博客_vue中computed和watch的区别computed(计算属性)与watch(侦听器),是Vue中常用的属性,那么什么时候该如何computed,什么时候该使用watch呢?https://blog.csdn.net/czjl6886/article/details/121454266?spm=1001.2014.3001.5502

slot插槽的理解与使用

slot 插槽,可以理解为slot在组件模板中提前占据了位置。当复用组件时,使用相关的slot标签时,标签里的内容就会自动替换组件模板中对应slot标签的位置,作为承载分发内容的出口。

具体细节参考文章

Vue 插槽(slot)详细介绍(对比版本变化,避免踩坑)_前端不释卷leo的博客-CSDN博客Vue中的插槽(slot)在项目中用的也是比较多的,今天就来介绍一下插槽的基本使用以及Vue版本更新之后的插槽用法变化。插槽是什么?插槽就是子组件中的提供给父组件使用的一个占位符,用<slot></slot> 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot>标签。简单理解就是子组件中留下个“坑”,父组件可以使用指定内容来补“坑”。以下举例子帮助理解。怎么使用插槽?基本用法https://blog.csdn.net/qq_41809113/article/details/121640035?spm=1001.2014.3001.5502

Vue.$delete和delete的区别

Vue.$delete 是直接删除了元素,改变了数组的长度;delete 是将被删除的元素变成内 undefined ,其他元素键值不变。

Vue.$set如何解决对象新增属性不能响应的问题

Vue.$set的出现是由于Object.defineProperty的局限性:无法检测对象属性的新增或删除。

源码位置:vue/src/core/observer/index.js

export function set(target, key, val) {
  // 数组
  if(Array.isArray(target) && isValidArrayIndex(key)) {
    // 修改数组长度,避免索引大于数组长度导致splice错误
    target.length = Math.max(target.length, key)
    // 利用数组splice触发响应
    target.splice(key, 1, val)
    return val
  }
  // key 已经存在,直接修改属性值
  if(key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = target.__ob__
  // target 不是响应式数据,直接赋值
  if(!ob) {
    target[key] = val
    return val
  }
  // 响应式处理属性
  defineReactive(ob.value, key, val)
  // 派发更新
  ob.dep.notify()
  return val
}

具体原理

1、若是数组,直接使用数组的 splice 方法触发响应式

2、若是对象,判断属性是否存在,对象是否是响应式

3、以上都不满足,最后通过 defineReactive 对属性进行响应式处理

具体细节参考文章

Vue 全局API 详细介绍(nextTick、set、delete、......)_前端不释卷leo的博客-CSDN博客Tips:Vue全局(内置)API,在实例中对应使用方式this.$apiName。Vue.extend(options)使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。data选项是特例,需要注意 - 在Vue.extend()中它必须是函数。// 创建构造器var Greet = Vue.extend({ template: '<p>{ {firstName}} { {lastName}} say { {alias}}</p>',...https://blog.csdn.net/qq_41809113/article/details/121581605?spm=1001.2014.3001.5502

Vue.$nextTick的原理

nextTick:在下次 DOM 更新循环结束之后执行延迟回调。常用于修改数据后获取更新后的DOM。

源码位置:vue/src/core/util/next-tick.js

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

// 是否使用微任务标识
export let isUsingMicroTask = false

// 回调函数队列
const callbacks = []
// 异步锁
let pending = false

function flushCallbacks () {
  // 表示下一个 flushCallbacks 可以进入浏览器的任务队列了
  pending = false
  // 防止 nextTick 中包含 nextTick时出现问题,在执行回调函数队列前,提前复制备份,清空回调函数队列
  const copies = callbacks.slice(0)
  // 清空 callbacks 数组
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

let timerFunc

// 浏览器能力检测
// 使用宏任务或微任务的目的是宏任务和微任务必在同步代码结束之后执行,这时能保证是最终渲染好的DOM。
// 宏任务耗费时间是大于微任务,在浏览器支持的情况下,优先使用微任务。
// 宏任务中效率也有差距,最低的就是 setTimeout
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 将 nextTick 的回调函数用 try catch 包裹一层,用于异常捕获
  // 将包裹后的函数放到 callback 中
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // pengding 为 false, 执行 timerFunc
  if (!pending) {
    // 关上锁
    pending = true
    timerFunc()
  }
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

总结:

1、运用异步锁的概念,保证同一时刻任务队列中只有一个 flushCallbacks。当 pengding 为 false 的时候,表示浏览器任务队列中没有 flushCallbacks 函数;当 pengding 为 true 的时候,表示浏览器任务队列中已经放入 flushCallbacks;待执行 flushCallback 函数时,pengding 会被再次置为 false,表示下一个 flushCallbacks 可进入任务队列

2、环境能力检测,选择可选中效率最高的(宏任务/微任务)进行包装执行,保证是在同步代码都执行完成后再去执行修改 DOM 等操作

3、flushCallbacks 先拷贝再清空,为了防止nextTick嵌套nextTick导致循环不结束

具体细节参考如上文章

虚拟DOM的理解

虚拟 DOM 的出现解决了浏览器的性能问题。虚拟 DOM 是一个用 JS 模拟的 DOM 结构对象(Vnode),用于频繁更改 DOM 操作后不立即更新 DOM,而是对比新老 Vnode,更新获取最新的Vnode,最后再一次性映射成真实的 DOM。这样做的原因是操作内存中操作 JS 对象速度比操作 DOM 快很多。

举个例子

<div id="container">
  <p>real dom </p>
  <ul>
    <li class="item">item 1</li>
    <li class="item">item 2</li>
    <li class="item">item 3</li>
  </ul>
</div

用 JS 来模拟以上 DOM 节点实现虚拟 DOM

function Element(tagName, props, children) {
  this.tageName = tagName
  this.props = props || {}
  this.children = children || []
  this.key = props.key
  let count = 0
  this.children.forEach(child => {
    if(child instanceof Element) count += child.count
    count++
  })
  this.count = count
}
const tree = Element('div', { id: container }, [
  Element('p', {}, ['real dom'])
  Element('ul', {}, [
    Element('li', { class: 'item' }, ['item1']),
    Element('li', { class: 'item' }, ['item2']),
    Element('li', { class: 'item' }, ['item3'])
  ])
])

虚拟 DOM 转为真实的节点

Element.prototype.render = function() {
  let el = document.createElement(this.tagName)
  let props = this.props
  for(let key in props) {
    el.setAttribute(key, props[key])
  }
  let children = this.children || []
  children.forEach(child => {
    let child = (child instanceof Element) ? child.render() : document.createTextNode(child)
    el.appendChild(child)
  })
  return el
}

Diff算法原理

具体细节参考文章

Vue中的Diff算法_Hayden-CSDN博客_diff算法Vue中的Diff算法本篇文章主要介绍Diff算法的思想和Vue中对Diff算法的基本实现。1. 为什么要用Diff算法由于在浏览器中操作DOM的代价是非常“昂贵”的,所以才在Vue引入了Virtual DOM,Virtual DOM是对真实DOM的一种抽象描述,不懂的朋友可以自行查阅相关资料。即使使用了Virtual DOM来进行真实DOM的渲染,在页面更新的时候,也不能全量地将整颗Vi...https://blog.csdn.net/qq_34179086/article/details/88086427?ops_request_misc=&request_id=&biz_id=102&utm_term=vue%20diff%E5%8E%9F%E7%90%86&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-8-88086427.pc_search_mgc_flag&spm=1018.2226.3001.4187

key的作用

key 是 Vue 中 vnode 的唯一标记,我们的 diff 的算法中 sameVnode 和 updateChildren 中就使用到了 key。

sameVnode 用来判断是否为同一节点。常见的业务场景是一个列表,若 key 值是列表索引,在新增或删除的情况下会存在就地复用的问题。(简单说,复用了上一个在当前位置元素的状态)所以 key 值的唯一,确保 diff 更准确。

updateChildren 中当其中四种假设都未匹配,就需要依赖老节点的 key 和 索引创建关系映射表,再用新节点的 key 去关系映射表去寻找索引进行更新,这保证 diff 算法更加快速。

简单来说就是:根据key判断是否复用,实现高效渲染

动态组件 和 异步组件

动态组件通过is特性实现。适用于根据数据、动态渲染的场景,即组件类型不确定。

具体细节参考官网

动态组件 & 异步组件 — Vue.jsVue.js - The Progressive JavaScript Frameworkhttps://cn.vuejs.org/v2/guide/components-dynamic-async.html

Vue.directive 有写过么,有哪些应用场景

Vue.directive 可以注册全局指令和局部指令。

指令定义函数提供如下钩子函数

1、bind:指令第一次绑定到元素时调用(只调用一次)

2、inserted: 被绑定元素插入父节点时使用(父节点存在即可调用)

3、update:被绑定元素所在模板更新时调用,不论绑定值是否变化。通过比较更新前后的绑定值

4、 componentUpdated: 被绑定元素所在模板完成一次更新周期时调用

5、unbind: 只调用一次,指令与元素解绑时调用

具体细节参考官网

自定义指令 — Vue.jsVue.js - The Progressive JavaScript Frameworkhttps://cn.vuejs.org/v2/guide/custom-directive.html

Vue 过滤器

Vue 过滤器可用在两个地方:双花括号插值和 v-bind 表达式。

Vue3 中已经废弃这个特点。

过滤器分为 全局过滤器 和 局部过滤器。

局部过滤器

<template>
  <div>{
   
   { name | formatName }}</div>
</template>
<script>
export default {
  // 局部过滤器
  filters: {
    formatName: function(value) {
      // 可基于源值做一些处理
      return value
    }
  }
}
</script>

全局过滤器

Vue.filter('formatName', function(value) {
  // 可基于源值做一些处理
  return value
})

过滤器可串联,执行顺序从左到右,第二个过滤器输入值是第一个过滤器的输出值

<div>{
   
   { name | formatName1 | formatName2 }}</div>

关于mixin的理解,有什么应用场景

mixin 混入分全局混入和局部混入,本质是 JS 对象,如 data、components、computed、methods 等。

全局混入不推荐使用,会影响后续每个Vue实例的创建。局部混入可提取组件间相同的代码,进行逻辑复用。

适用场景:如多个页面具备相同的悬浮定位浮窗,可尝试用 mixin 封装。

具体细节参考文章 

Vue 混入(mixin)详细介绍(可复用性、全局混入)_前端不释卷leo的博客-CSDN博客基础混入(mixin)提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项(如data、methods、mounted等等)。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。基本使用1、定义一个混入(mixin)let mixin = { created() { console.log("我是mixin里面的created!") }, methods: { hello() { .https://blog.csdn.net/qq_41809113/article/details/121912330?spm=1001.2014.3001.5502

介绍一下keep-alive

具体细节参考文章

Vue keep-alive 详细介绍(动态组件、路由组件缓存优化)_前端不释卷leo的博客-CSDN博客用法keep-alive 是Vue的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 transition 相似,keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。当组件在<keep-alive>内被切换,它的activated和deactivated这两个生命周期钩子函数将会被对应执行。在 2.2.0 及其更高版本中,activated和deactivated将会在<keep-ali...https://blog.csdn.net/qq_41809113/article/details/121959533?spm=1001.2014.3001.5502

Vue-Router 配置 404 页面

* 代表通配符,若放在任意路由前,会被先匹配,导致跳转到 404 页面,所以需将如下配置置于最后

{
  path: '*',
  name: '404'
  component: () => import('./404.vue')  
}

Vue-Router 有哪几种导航守卫

全局路由守卫

在路由跳转前触发,可在执行 next 方法前做一些身份登录验证的逻辑

const router = new VueRouter({
  // 相关配置
})

// 全局路由守卫(跳转每个路由页面前统一处理)
router.beforeEach((to, from, next) => {
  ...
  // 必须执行 next 方法来触发路由跳转 
  next() 
})

全局后置钩子

和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身

// 全局后置钩子(跳转每个路由页面后统一处理)
router.afterEach((to, from) => {
  // ...
})

路由独享守卫

可在路由配置上直接定义 beforeEnter

const router = new VueRouter({
  routes: [
    {
      path: '/home',
      component: Home,
      // 只有该路由独享
      beforeEnter: (to, from, next) => {
      
      }
    },
    {
      path: '/about',
      component: About
    }
  ]
})

组件守卫

组件内可直接定义如下路由导航守卫

onst Foo = {
  template: `...`,
  beforeRouteEnter(to, from, next) {
    // 不能获取组件实例 this
    // 当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate(to, from, next) {
    // 当前路由改变,但是组件被复用时调用
    // 可访问实例 this
  },
  beforeRouteLeave(to, from, next) {
    // 导航离开组件时被调用
  }
}

更多细节参考官网

API 参考 | Vue Routerhttps://router.vuejs.org/zh/api/#router-link

Vue-Router 完整的导航解析流程

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 的回调函数,创建好的组件实例会作为回调函数的参数传入

推荐本人对Vue router的详细介绍博客

Vue 路由(Router)详细介绍(切换,传参,通信······)_前端不释卷leo的博客-CSDN博客前言:在一个vue项目中,免不了对组件(页面)进行切换与跳转。而用 Vue.js + Vue Router 创建单页应用,感觉很自然:使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 Vue Router 添加进来,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们。话不多说,直接开始!!!准备工作在使用脚手架vue-cli3创建(vue create projectName)一个vue项目时,https://blog.csdn.net/qq_41809113/article/details/121841075?spm=1001.2014.3001.5502

Vuex 的理解及使用

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,采用集中式存储管理应用的所有组件的状态。

主要解决如下 两个 问题

1、多个视图依赖同一状态。

2、来自不同视图的行为需要变更同一个状态。

其包含如下模块

vuex

State:定义并初始化全局状态。
Getter:依赖 State 中的状态,进行二次包装,不会影响 State 源数据。
Mutation:更改 State 状态的函数,必须是同步。
Action:用于提交 Mutation,可包含任意异步操作。
Module:若应用复杂,Store 会集中一个比较大的对象而显得臃肿,Module允许我们将 Store模块化管理。

当然,若应用比较简单,共享状态也比较少,可以用 Vue.observe 去替代 Vuex,省去安装一个库也挺好。

具体细节参考文章

Vuex核心用法,一学就会!(中秋特辑,快来get你的月饼啦)_前端不释卷leo的博客-CSDN博客今天是中秋佳节,祝各位大佬中秋节快乐!来get自己的月饼吧。趁着这个美好的节日,怀着美丽的心情,建造自己的“工厂”,我将以个人理解,用“月饼工厂”,模拟vuex的精简用法,嘻嘻嘻......话不多说,直接“造”(燥)起来。...https://blog.csdn.net/qq_41809113/article/details/120398625?spm=1001.2014.3001.5502

Vuex 刷新后数据丢失怎么办

持久化缓存:localStorage、sessionStorage

Vuex 如何知道 State 是通过 Mutation 修改还是外部修改

Vuex 中修改 state 唯一渠道是执行 commit 方法,底层通过执行 this._withCommit(fn),且设置_committing标识符为 true,才能修改 state,修改完还需要将标识符置为 false。外部修改是无法设置标识位的,所以通过 watch 监听 state 变化,来判断修改的合法性。

Vue SSR 了解么

Vue SSR 项目中暂时还没有运用过,后续会写个 Demo 单独成文吧。这边搬运下其他答案。

SSR 服务端渲染,将 HTML 渲染工作放在服务端完成后,将 HTML 返回到浏览器端。

优点:SSR有更好的 SEO,首屏加载更快。
缺点:服务端负载大。

如果是内部系统,SSR其实没有太多必要。如果是对外的项目,维护高可用的node服务器是个难点。

Vue2 与 Vue3 的区别 ?Vue3有哪些优化点?

具体细节参考本人博客对Vue3的详细介绍

Vue 性能优化

1、非响应式数据通过 Object.freeze 冻结数据

2、嵌套层级不要过深

3、computed 和 watch 区别使用

4、v-if 和 v-show 区别使用

5、v-for 避免和 v-if 一起使用,且绑定 key 值要唯一

6、列表数据过多采用分页或者虚拟列表

7、组件销毁后清除定时器和事件

8、图片懒加载

9、路由懒加载

10、防抖、节流

11、按需引入外部库

12、keep-alive缓存使用

13、服务端渲染(SSR)和预渲染

猜你喜欢

转载自blog.csdn.net/qq_41809113/article/details/122158184