Vue知识点整理=^•ω•^=

3,699 阅读15分钟

这是我个人阅片(帖)无数以及在学习中总结的,希望能帮到大家,希望能给个星星Thanks♪(・ω・)ノ

基础篇

说说你对MVVM的理解

  • Model-View-ViewModel的缩写,Model代表数据模型,View代表UI组件,ViewModel将Model和View关联起来

  • 数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据

MVC和MVVM的区别

  • MVC指的是Model-View-Controller,即模型-视图-控制器。
  • MVC中的Control在MVVM中演变成viewModel.
  • MVVM通过数据来显示视图,而不是通过节点操作
  • MVVM主要解决了MVC中大量的DOM操作,使页面渲染性能降低,加载速度慢,影响用户体验的问题

虚拟Dom

由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的产生原因。

  • Virtual DOM本质就是用一个原生的JS对象去描述一个DOM节点。是对真实DOM的一层抽象。(也就是源码中的VNode类,它定义在src/core/vdom/vnode.js中。)

  • 虚拟 DOM 的实现原理主要包括以下 3 部分:

    • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
    • diff 算法 — 比较两棵虚拟 DOM 树的差异;
    • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
  • key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速

key的作用

  • key是为每个vnode指定唯一的id,在同级vnode的Diff过程中,可以根据key快速的进行对比,来判断是否为相同节点,

  • 利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,指定key后,可以保证渲染的准确性(尽可能的复用 DOM 元素。)

为什么不建议用index作为key?

  • 不建议 用index 作为 key,和没写基本上没区别,因为不管你数组的顺序怎么颠倒,index 都是 0, 1, 2 这样排列,导致 Vue 会复用错误的旧子节点,做很多额外的工作

  • 一般都用数据中的唯一值,比如ID这种,或者实在不行使用UUID库,  

diff的步骤

  • 用JS模拟真实DOM节点

  • 把虚拟DOM转换成真实DOM插入页面中

  • 发生变化时,比较两棵树的差异,生成差异对象

  • 根据差异对象更新真实DOM

Vue2.x响应式数据/双向绑定原理

Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据。其中,View变化更新Data,可以通过事件监听的方式来实现,所以 Vue数据双向绑定的工作主要是如何根据Data变化更新View。

  • 大概流程如下
    • 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

    • getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在属性被访问和修改时通知变更。

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

响应式原理

采用数据劫持结配合发布者-订阅者模式的方法,通过Object.defineProperty()来劫持各个属性的getter、setter属性,在数据变动后,在数据发生变化的时候,发布消息给依赖收集器Dep,去通知观察者Watcher,做出对应的回调函数去更新视图。

  • 解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。

  • 监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。

  • 订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新——这是一个典型的观察者模式订阅器

  • Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

vue2.x中如何监测数组和对象变化?

  • Object通过Object.defineProperty结合递归就能实现

    • 比较麻烦就对了,Proxy就直接代理整个对象
  • Array的话 Vue将data中的数组进行了原型链重写,指向了自己定义的数组原型方法。

  • 数组某些情况下会不刷新视图,我们这样解决

    • 当利用索引直接设置一个数组项时,例如vm.items[indexOfItem] = newValue
    //使用该方法进行更新视图
    // vm.$set,Vue.set的一个别名
    vm.$set(vm.items, indexOfItem, newValue)
    
    • 当修改数组的长度时,例如vm.items.length = newLength
    //使用该方法进行更新视图
    // Array.prototype.splice
    vm.items.splice(indexOfItem, 1, newValue)
    

Proxy比Object.defineProperty好在哪?

Vue3.x改用Proxy替代Object.defineProperty

  • Object.defineProperty 只能劫持对象属性的getter和setter方法

    • Proxy 是直接代理劫持整个对象
  • Object.definedProperty 不支持数组(可以监听数组,不过数组方法无法监听自己重写),更准确的说是不支持数组的各种API(所以VUE重写了数组方法

    • Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。
  • Proxy 会返回一个代理对象,我们只需要操作新对象即可,而 Object.defineProperty 只能遍历对象属性直接修改

  • Object.definedProperty唯一比Proxy好的一点就是兼容性,不过相信Proxy新标准将受到浏览器厂商重点持续的性能优化

生命周期篇

生命周期四个阶段

初始化 (create)--- 组件挂载(mount)-----组件更新 (update)--- 销毁(destroy)

生命周期 发生了什么
beforeCreate 初始化界面前 : 在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问
created 初始化界面后 : 在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数,也就是不会更新视图,SSR可以放这里。
beforeMount 挂载前 :完成模板编译,虚拟Dom已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发updated
mounted 挂在完成 : 将编译好的模板挂载到页面 (虚拟DOM挂载) ,可以在这进行异步请求以及DOM节点的访问,在vue用$ref操作
beforeUpdate 更新数据前 : 组件数据更新之前调用,数据都是新的,页面上数据都是旧的 组件即将更新,准备渲染页面 , 可以在当前阶段进行更改数据,不会造成重渲染
updated 组件更新后 : render重新渲染 , 此时数据和界面都是新的 ,要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新
beforeDestroy    组件卸载前 : 实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器
destroyed 组件卸载后 : 组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
activited keep-alive 专属 , 组件被激活时调用
deactivated keep-alive 专属 , 组件被销毁时调用

初次渲染就会触发的生命周期

  • beforeCreate() , created()
  • beforeMount() , mounted()

父组件和子组件之间的生命周期执行顺序

组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。 组件的销毁操作是先父后子,销毁完成的顺序是先子后父。

加载渲染过程 子组件在父组件的beforeMount和Mounted之间渲染

- 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

子组件更新过程

- 父beforeUpdate->子beforeUpdate->子updated->父updated

父组件更新过程

- 影响到子组件: - 父beforeUpdate -> 子beforeUpdate->子updated -> 父updted
- 不影响子组件: - 父beforeUpdate -> 父updated

销毁过程

- 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

什么阶段才能调用DOM

  • 在钩子函数 mounted 被调用前,Vue 已经将编译好的模板挂载到页面上,所以在 mounted 中可以访问操作 DOM。

什么阶段能发起请求

  • 可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

  • 但是推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

    • 能更快获取到服务端数据,减少页面loading 时间;
    • ssr不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

常规篇

computed 和 watch 的区别以及应用场景?

  • computed 依赖其他的值,且具有缓存,缓存变化才会更新

    • 只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 进行数值计算,并且依赖于其它数据 用他
  • watch 没有缓存 监听某一个值 变化进行一些操作

    • 数据变化时执行异步或开销较大的操作时 用它

v-if和v-show的区别以及应用场景?

  • v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;

    • v-if移除Dom,对其进行销毁;
  • v-show 则适用于需要非常频繁切换条件的场景。

    • v-show是对元素进行display:none;

为什么 v-for 和 v-if 不建议用在一起?

  • v-for优先级高于v-if,如果连在一起使用的话会把v-if给每一个元素都添加上,重复运行于每一个v-for循环中,会造成性能浪费

  • 可以将v-if写在v-for的外层

v-model的实现

  • v-model是用来在表单控件或者组件上创建双向绑定的

  • 他的本质是v-bind和v-on的语法糖

<input v-model="sth" />
//  等同于
<input :value="sth" @input="sth = $event.target.value" />

Vue事件绑定原理是什么?

  • 原生事件绑定是通过addEventListener绑定给真实元素的。

  • 组件事件绑定是通过Vue自定义的key$on实现的。

nextTick的实现原理是什么?

在下次DOM更新循环结束之后执行的延迟回调。

  • 根据执行环境分别尝试采用 用微任务,再是宏任务
Promise的then -> MutationObserver的回调函数 -> setImmediate -> setTimeout 是否存在,找到存在的就调用他childrenRef

Vue2.x组件通信有哪些方式?

过阵子会专门写一篇Vue通信 >v<

  • 父子组件通信
    - 事件机制(父->子props,子->父$on、$emit)
    - 获取父子组件实例$parent、$children获取实例的方式调用组件的属性或者方法   
    - Provide、inject (不推荐使用,组件库时很常用)
  • 兄弟组件通信
  - eventBus 这种方法通过一个空的 Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件

  - 事件总线Vue.prototype.$bus = new Vue

  - Vuex
  • 跨级组件通信
  - Vuex
  - $attrs父->子孙  / $listens子孙->获父

Vue 的单向数据流

  • 所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态.

  • 子组件为什么不可以修改父组件传递的Prop?

    • 这是为了防止意外的改变父组件状态,使得应用的数据流变得难以理解。
    • 如果破坏了单向数据流,当应用复杂时,debug 的成本会非常高。

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

因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;

  • 而且new Vue根组件基本不需要复用,因此不需要以函数方式返回

VUE模板编译原理?

  • 简单来说

    • 1.解析模板 生成语法树

    • 2.对语法树进行标记,用来做虚拟Dom的优化

    • 3.将语法树转换为可执行代码

  • 细细细

  • 第一步将模版字符串转换成element ASTs(解析器) 首先解析模版,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。

  • 第二步是对AST进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器) 那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记,用来做虚拟DOM的渲染优化

  • 第三步是使用element ASTs生成render函数代码字符串(代码生成器) 编译的最后一步是将优化后的AST树转换为可执行的代码。

Vue中 v-html 有什么作用?会导致什么问题?

  • v-html可以用来识别HTML标签并渲染出去

  • 导致问题: 在网站上动态渲染任意Html,很容易导致受到Xss攻击,所以只能在可信内容上使用v-html,且永远不能用于用户提交的内容上

  • XSS防范手段

    • 设置Cookie httpOnly为严格模式 , 禁止Javascript通过document.cookie获得
    • 对所有的输入做严格的校验尤其是在服务器端,过滤掉任何不合法的输入,比如手机号必须是数字,通常可以采用正则表达式.
    • 转义单引号,双引号,尖括号等特殊字符,
    • 净化和过滤掉不必要的html标签,脚本的a标签的href或者onclick等,
    • 配置白名单CSP,阻止白名单以外的资源加载和运行

什么是 什么是 mixin ?

  • Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。如果你希望再多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。

  • 然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。

const mixin1 = {mounted() {console.log(2);}};
const mixin2 = {mounted() { console.log(3); }};
const extend1 ={mounted() {console.log(4);}}
export default{
    extends:extend1,
    mixins: [mixin1, mixin2],
    mounted() {
    console.log(1);
  }
}
//最终输出 4 2 3 1

hash / history 两种模式有什么区别?

  • 最明显的是在显示上,hash模式的URL中会夹杂着#号,而history没有。

  • Vue底层对它们的实现方式不同。

    • hash模式是依靠onhashchange事件(监听location.hash的改变)
    • history模式是主要是依靠的HTML5 history中新增的两个方法
      • pushState()可以改变url地址且不会发送请求
      • replaceState()可以读取历史记录栈,还可以对浏览器记录进行修改。
  • 当真正需要通过URL向后端发送HTTP请求的时候,比如常见的用户手动输入URL后回车,或者是刷新(重启)浏览器,这时候history模式需要后端的支持。

  • 因为history模式下,前端的URL必须和实际向后端发送请求的URL一致,例如有一个URL是带有路径path的(例如www.lindaidai.wang/blogs/id),如果后端没有对这个路径做处理的话,就会返回404错误。所以需要后端增加一个覆盖所有情况的候选资源,一般会配合前端给出的一个404页面。

附上我用Node处理history问题的js代码块 >v<

// 其实就是用了个中间件 -.-
let history = require('connect-history-api-fallback');
app.use(history())

VueX篇

VueX是什么?

  • Vuex 类似 Redux 的状态管理器,用来管理Vue的所有组件状态。

    • state 定义了应用状态的数据结构,可以在这里设置默认的初始状态

      • 图书馆
    • mutations 同步操作,更改store状态

      • 图书管理员
    • actions 用于提交 mutation,而不是直接变更状态,可以包含任意异步操作

      • 借书卡,借书动作
    • Module 允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

      • 图书馆的楼层,小图书馆就不需要vuex了 o.o
    • Getter 处理一些比较复杂的函数

      • 图书馆的电脑?
    • component 组件

      • 借书人

    以上是我便于快速理解的 看着玩就好了ヽ(・ω・´メ)

Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。 ——Vuex官方文档

什么情况下使用 Vuex?

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

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

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

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

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

为什么不直接分发mutation,而要通过分发action之后提交 mutation变更状态

  • mutation 必须同步执行,我们可以在 action 内部执行异步操作
  • 可以进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的副作用(即状态变更)

我觉得把同步异步分开好维护好管理吧

vuex的action有返回值吗?返回的是什么?

  • store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise

  • Action 通常是异步的,要知道 action 什么时候结束或者组合多个 action以处理更加复杂的异步流程,可以通过定义action时返回一个promise对象,就可以在派发action的时候就可以通过处理返回的 Promise处理异步流程

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

Vue.observable(简易版VueX)

  • 2.6.0 新增 用法:让一个对象可响应,利用Vue.observable实现一个简易的 vuex
store.js
import Vue from 'vue'

// 通过Vue.observable创建一个可响应的对象
export const store = Vue.observable({
  userInfo: {},
  roleIds: []
})

// 定义 mutations, 修改属性
export const mutations = {
  setUserInfo(userInfo) {
    store.userInfo = userInfo
  },
  setRoleIds(roleIds) {
    store.roleIds = roleIds
  }
}
  • 使用vuex
<template>
  <div>
    {{ userInfo.name }}
  </div>
</template>
<script>
import { store, mutations } from '../store'
export default {
  computed: {
    userInfo() {
      return store.userInfo
    }
  },
  created() {
    mutations.setUserInfo({
      name: '子君'
    })
  }
}
</script>

性能优化篇

提高首频加载速度

  • Vue-Router路由懒加载(利用Webpack的代码切割)
  • 使用CDN加速,将通用的库从vendor进行抽离
  • Nginx的gzip压缩
  • Vue异步组件
  • 服务端渲染SSR
  • 如果使用了一些UI库,采用按需加载
  • Webpack开启gzip压缩
  • 如果首屏为登录页,可以做成多入口
  • Service Worker缓存文件处理
  • 使用link标签的rel属性设置 prefetch(这段资源将会在未来某个导航或者功能要用到,但是本资源的下载顺序权重比较低,prefetch通常用于加速下一次导航)、preload(preload将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度)

keep-alive

  • 可以在组件切换时 ,保留组件状态,避免重新渲染 , 缓存在内存
<transition>
    <keep-alive>
      <component v-bind:is="currentTabComponent"></component>
      //<router-view><router-view>
    </keep-alive>
</transition>

v-cloak 网速慢时用

  • 网速慢的情况下,在使用vue绑定数据的时候,渲染页面时会出现变量闪烁
  • 可以解决闪烁,但是会出现白屏,这样可以结合骨架屏使用
<div class="#app" v-cloak>
    <p>{{value.name}}</p>
</div>

v-once 只渲染一次,且不会改变

<span v-once> 这时只需要加载一次的标签</span>

路由懒加载/组件懒加载

component:()=>import('xxx')

图片懒加载Vue-lazyload

over 边复习边调整边总结,为了面试能过冲冲冲啊