Vue 重点知识总结

1,581 阅读5分钟

前言

由于很多小伙伴看了 《普通人如何进大厂》这篇文章对我的笔记比较感兴趣,所以我花时间整理提炼了一下跟大家分享,不保证内容完全正确,仅供参考哈

其他笔记传送门

Vue 双向绑定原理

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

数据劫持

遍历 data 属性通过Object.defineproprety 劫持 setter 和 getter

vue 依赖收集过程

  1. new wacther 时,通过 pushTarget 把该 Dep.target 指向改 wacther,在 render 执行渲染 Vnode 过程中触发了 getter
function pushTarget (_target: ?Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  Dep.target = _target
}

updateComponent = () => {
 vm._update(vm._render(), hydrating)
}

new Watcher(vm, updateComponent, noop, {
  before () {
    if (vm._isMounted) {
    callHook(vm, 'beforeUpdate')
   }
}}, true /* isRenderWatcher */)
  1. getter 触发 dep.depend() 即执行 Dep.target.addDep(this) ,即执行wacther 里面的 addDep 方法
// Dep 类
depend () {
 if (Dep.target) { // Dep.target =》watcher
  Dep.target.addDep(this) // this =》dep
 }
}
  1. dep作为参数在 addDep 里通过map has 判断 dep 的 id 是否已经存在了 dep,如果没有就压入栈 (过滤重复的dep)
  2. 后执行 刚才传入的 dep 的 addSub 方法收集wacther
// Watcher 类

addDep (dep: Dep) {
 const id = dep.id
 if (!this.newDepIds.has(id)) {
 this.newDepIds.add(id)
 this.newDeps.push(dep)
 if (!this.depIds.has(id)) { // 防止重复收集 dep
    dep.addSub(this) // dep 收集 watcher
   }
}}

watch 和 computed 的区别

  • computed 用于计算产出新的数据,有缓存
  • computed 适合比较耗性能的计算场景
  • watch 更多是监听,监听某个变化而执行回调

Vue key 的作用

key 是给每一个 vnode 的唯一 id,可以依靠 key,更准确, 更快的拿到 oldVnode 中对应的 vnode 节点。

  • 更准确

因为带 key 就不是就地复用了,在sameNode函数 a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。

  • 更快

利用 key 的唯一性生成map对象来获取对应节点,比遍历方式更快

不带 key 时或者以 index 作为 key 时:比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位

Vue nextTick 原理

  • 它可以在 DOM 更新完毕之后执行一个回调,使我们可以操作更新后的 dom

  • 可以监听 dom 的变化就是 h5 新特性的 mutationObserver,但是 Vue 并不是通过监听 dom 变化的方式实现的

  • 而是通过 eventloop 原理,因为 eventloop 的 task 执行完后(完成一次事件循环)进行一次 DOM 更新

  • 而完成一个 task 分界点就是 微任务完成 所以 Vue 首先是用 promise.then 然后就是 mutationObserver。

  • 为了兼容性然后降级到宏任务 setImmediate 和 setTimeout

// timerFunc 收集异步 task 事件
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
let timerFunc

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)
  }
}

// 用 callbacks 收集回调函数
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

diff 算法流程(patch)

第一次走 createElm 生成真实 dom,以后通过 sameVnode 判断同结点则进行 patchVNode 结点的 child ,如果新旧结点都存在则走 updateChildren 流程,不断通过新旧头头、尾尾、交叉 sameVnode 对,都比对不成功 ,则直接拿key去映射表查找,如找到有相同的 key 结点则进行 sameVnode 对比,成立则又进入下子结点的patchVNode,直到遍历完成。

new vue() 流程

image.png

Vuex 有什么特点

首先说明 Vuex 是一个专为 Vue 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 核心概念重点同步异步实现 action mutation

Vue 组件为什么 data 不能是对象

Vue 组件可能存在多个实例,如果使用对象形式定义 data,则会导致它们共用一个data对象,那么状态变更将会影响所有组件实例,

mvvm 的理解

通过指令的方式实现 model 和 view 主动数据响应和view更新

Vue 有哪些性能优化

  • 路由懒加载

  • keep-alive 缓存页面

  • 用 v-show 复用dom

  • 长列表纯数据展示用 Object.freeze 冻结

  • 长列表大数据用虚拟滚动

  • 事件及时销毁

  • 服务端渲染 ssr

服务端渲染原理

在客户端请求服务器的时候,服务器到数据库中获取到相关的数据,并且在服务器内部将Vue组件渲染成HTML,并且将数据、HTML一并返回给客户端,这个在服务器将数据和组件转化为HTML的过程,叫做服务端渲染SSR

使用SSR的好处

  1. 有利于SEO

其实就是有利于爬虫来爬你的页面,因为部分页面爬虫是不支持执行JavaScript的,这种不支持执行JavaScript的爬虫抓取到的非SSR的页面会是一个空的HTML页面,而有了SSR以后,这些爬虫就可以获取到完整的HTML结构的数据,进而收录到搜索引擎中。

  1. 白屏时间更短

相对于客户端渲染,服务端渲染在浏览器请求URL之后已经得到了一个带有数据的HTML文本,浏览器只需要解析HTML,直接构建DOM树就可以。而客户端渲染,需要先得到一个空的HTML页面,这个时候页面已经进入白屏,之后还需要经过加载并执行JavaScript、请求后端服务器获取数据、JavaScript 渲染页面几个过程才可以看到最后的页面。特别是在复杂应用中,由于需要加载 JavaScript 脚本,越是复杂的应用,需要加载的 JavaScript 脚本就越多、越大,这会导致应用的首屏加载时间非常长,进而降低了体验感。

Vue3 有什么新特性

  • 数据劫持:用 proxy 做代理
  • 虚拟 dom 重构:v2会把静态结点转成vdom,新只会构造动态结点的vdom
  • 将大多数全局API和内部组件移至ES模块导出,tree-shaking更友好
  • 支持了 ts
  • Composition api:新增 setup 函数有利于代码复用(替代mixin,mixin会容易存在命名冲突)

Vue2 响应式弊端

响应化过程需要递归遍历,消耗较大新加或删除属性无法监听数组响应化需要额外实现Map、Set、Class等无法响应式修改语法有限制

生命周期2.x与Composition之间的映射关系

  • beforeCreate -> use setup()

  • created -> use setup()

  • beforeMount -> onBeforeMount

  • mounted -> onMounted

  • beforeUpdate -> onBeforeUpdate

  • updated -> onUpdated

  • beforeDestroy -> onBeforeUnmount

  • destroyed -> onUnmounted

  • errorCaptured -> onErrorCaptured

Vue 组件通信

  1. props★★(父传子)

  2. emit/emit/on★★事件总线(跨层级通信)

  3. vuex★★★(状态管理常用皆可)优点:一次存储数据,所有页面都可访问

  4. parent/parent/children(父=子项目中不建议使用)缺点:不可跨层级

  5. attrs/attrs/listeners(皆可如果搞不明白不建议和面试官说这一种)

  6. provide/inject★★★(高阶用法=推荐使用)优点:使用简单 缺点:不是响应式

v-model vs .sync

区别:

一个组件可以多个属性用.sync修饰符,可以同时"双向绑定多个“prop”,而并不像v-model那样,一个组件只能有一个

使用场景:

v-model针对更多的是最终操作结果,是双向绑定的结果,是value,是一种change操作

sync针对更多的是各种各样的状态,是状态的互相传递,是status,是一种update操作

v-model 可以在只需子组件更新父组件的场景使用如:

// 父组件接收
<CreativityGroup :planTemplateModel.sync="planParams"/> 
 // 子组件传值
@Watch('formModle', { deep: true })
  formModleChange(nVal) {
    this.$emit('input', nVal)
  }

v-model vs .sync 使用例子

<template>
  <div>
    my myParam {{ value }}<br />
    paramsync {{ paramsync }}
    <button type="button" @click="change">change my</button>
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: ""
    },
    paramsync: {
      type: Number,
      default: 0
    }
  },
  computed: {
    value1: {
      set(val) {
        this.$emit("input", val);
      },
      get() {
        return this.value;
      }
    }
  },
  methods: {
    change() {
      this.value1 = "rtrtr";
      console.log("this.value", this.value);
    //   this.paramsync = 78;
    // this.$emit('input','更新之后')
      this.$emit("update:paramsync",5555);
    }
  }
};
</script>

Vue 生命周期

  1. beforeCreate:在实例初始化之后,数据观测(data observe)和event/watcher事件配置之前被调用,这时无法访问data及props等数据;

  2. created:在实例创建完成后被立即调用,此时实例已完成数据观测(data observer),属性和方法的运算,watch/event事件回调,挂载阶段还没开始,$el尚不可用。

  3. beforemount:在挂载开始之前被调用,相关的render函数首次被调用。

  4. mounted:实例被挂载后调用,这时el被新创建的vm.el替换,若根实例挂载到了文档上的元素上,当mounted被调用时vm.el替换,若根实例挂载到了文档上的元素上,当mounted被调用时vm.el也在文档内。注意mounted不会保证所有子组件一起挂载。

  5. beforeupdata:数据更新时调用,发生在虚拟dom打补丁前,这时适合在更新前访问现有dom,如手动移除已添加的事件监听器。

  6. updated:在数据变更导致的虚拟dom重新渲染和打补丁后,调用该钩子。当这个钩子被调用时,组件dom已更新,可执行依赖于dom的操作。多数情况下应在此期间更改状态。如需改变,最好使用watcher或计算属性取代。注意updated不会保证所有的子组件都能一起被重绘。

  7. beforedestory:在实例销毁之前调用。在这时,实例仍可用。

  8. destroyed:实例销毁后调用,这时 Vue 实例的所有指令都被解绑,所有事件监听器被移除,所有子实例也被销毁。

Vue compile 过程

编译过程整体分为解析、优化和生成

解析 - parse

解析器将模板解析为抽象语法树,基于AST可以做优化或者代码生成工作

优化- optimize

优化器的作用是在AST中找出静态子树并打上标记。静态子树是在AST中永远不变的节点,如纯文本节点。标记静态子树的好处:每次重新渲染,不需要为静态子树创建新节点虚拟DOM中patch时,可以跳过静态子树

代码生成- generate

将AST转换成渲染函数中的内容,即代码字符串。

Vue2 vs Vue3

Vue2 响应式弊端:响应化过程需要递归遍历,消耗较大新加或删除属性无法监听数组响应化需要额外实现Map、Set、Class等无法响应式修改语法有限制

vite 原理

待更新