Vue重点知识点整理不容错过

目录
文章目录隐藏
  1. 基础篇
  2. 生命周期篇
  3. 常规篇
  4. VueX 篇
  5. 性能优化篇

基础篇

说说你对 MVVM 的理解

  • Model-View-ViewModel 的缩写,Model 代表数据模型,View 代表 UI 组件,ViewModel 将 Model 和 View 关联起来
  • 数据会绑定到 viewModel 层并自动将数据渲染到页面中,视图变化的时候会通知 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.$set,Vue.set 的一个别名
vm.$set(vm.items, indexOfItem, newValue)
//使用该方法进行更新视图
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
  • 当修改数组的长度时,例如 vm.items.length = newLength
  • 当利用索引直接设置一个数组项时,例如 vm.items[indexOfItem] = 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 组件通信有哪些方式?

父子组件通信

事件机制(父->子 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 底层对它们的实现方式不同。

  • pushState()可以改变 url 地址且不会发送请求
  • replaceState()可以读取历史记录栈,还可以对浏览器记录进行修改。
  • hash 模式是依靠 onhashchange 事件(监听 location.hash 的改变)
  • history 模式是主要是依靠的 HTML5 history 中新增的两个方法

当真正需要通过 URL 向后端发送 HTTP 请求的时候,比如常见的用户手动输入 URL 后回车,或者是刷新(重启)浏览器,这时候 history 模式需要后端的支持。

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

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

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

VueX 篇

VueX 是什么?

个人理解:Vuex 类似 Redux 的状态管理器,用来管理 Vue 的所有组件状态。

借书人

图书馆的电脑?

图书馆的楼层,小图书馆就不需要 vuex 了 o.o

借书卡,借书动作

图书管理员

图书馆

  • state 定义了应用状态的数据结构,可以在这里设置默认的初始状态
  • mutations 同步操作,更改 store 状态
  • actions 用于提交 mutation,而不是直接变更状态,可以包含任意异步操作
  • Module 允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
  • 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

图片懒加载 Vue-lazyload

文章来源:公众号【Vue 社区】

 

「点点赞赏,手留余香」

2

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » Vue重点知识点整理不容错过

发表回复