new Vue 发生了什么

  1. new Vue 的时候调用会调用 _init 方法
  2. 合并和初始化配置,初始化一些事件和方法,初始化生命周期
  3. 建立数据响应式系统
  4. 模板编译
  5. 挂载实例,渲染到页面中

https://ustbhuangyi.github.io/vue-analysis/v2/data-driven/new-vue.html

模板编译原理

用户传递 template 模板,Vue 通过这个 template 编译成 render 函数

  1. template 转 AST 树
  2. AST 树转 render 函数
  3. render 函数返回对应的虚拟 DOM
  4. 虚拟 DOM 生成真实 DOM

虚拟 DOM

定义:

虚拟 DOM(Virtual DOM)是一层对真实 DOM 的抽象,是由 JavaScript 对象(VNode 节点)构成的一个树,用对象的属性来描述节点,最终映射到真实的 DOM

核心思想:

通过维护一个虚拟的 DOM 树,将复杂的 DOM 操作抽象化。当应用状态发生变化时,虚拟 DOM 会首先在内存中生成一个新的虚拟 DOM 树,然后通过算法比较新旧两棵虚拟 DOM 树的差异,最后仅将需要更新的部分应用到真实 DOM 上,从而避免直接操作真实 DOM 带来的性能开销

优点:

  • DOM 操作是浏览器中比较耗性能的部分,虚拟 DOM 可以通过批量处理更新和最小化 DOM 操作次数,提高页面性能
  • 虚拟 DOM 是一种抽象的概念,不依赖于浏览器环境,它可以用于跨平台开发

diff 算法

用于虚拟 DOM 渲染成真实 DOM 的新旧 VNode 节点比较

两个特点:

  • 比较只会在同层级进行,不会跨层级比较
  • 比较的过程中,循环从两边向中间比较

key 的作用

用来对 vnode 进行唯一标识,帮助 diff 算法根据 key 值做新旧节点的比对,在进行数据变化时可以不用整个数据重新渲染

v-for 不建议使用 index 作为 key

使用 v-for 更新已渲染的元素列表时,默认是“就地复用”策略,如列表中有表单输入则会导致元素复用产生错位

响应式原理

  1. vue 初始化 data 时,会使用 Object.defineProperty() 对其中的属性进行劫持,深层次的属性采用递归劫持。同时会对每个属性初始化一个 dependency 实例
  2. 进行 html 模板解析,使用 Compile 函数,解析到其中的插值表达式时,进行 data 中数据的替换。模板每解析到一个属性,就初始化一个 watcher 实例(说明有一个地方在用这个属性),watcher 初始化时会在 Dependency 类上增加一个属性 target 指向 watcher 实例自己,同时 watcher 初始化时触发属性的 get()get() 中会把 target (也就是 watcher) 放入 dependency 实例的 subscribers 数组中
  3. 当属性值发生变化时,在 set() 中调用 dependency 实例的 notify() 方法,将 subscribers 数组中每个元素(也就是 watcher)执行 update()
  • data 中的每个属性(任何层级)都有一个 dependency 实例,一个 dependency 中对应着多个 watcher
  • 建立 dependencywatcher 的联系就是通过 Dependency.target 来实现的

为什么要有 Dependency.target

  • dependency 实例必须创建在 Observer 中(因为进行数据劫持时对每个属性都创建一个依赖对象)
  • watcher 实例必须创建在 Compile 中(因为只有哪些地方用到了才创建订阅)

所以需要一个中间桥梁来联系这 2 个实例,所以一个巧妙的办法是通过 watcher 初始化时触发属性的 getter ,并在 getter 中进行依赖的建立

Vue.js 数据双向绑定的原理及实现_哔哩哔哩_bilibili
blog/markdown/vue/vue2原理探索—响应式系统.md at master · LuckyWinty/blog

缺点

  • 无法监听动态属性增删
  • 无法直接监听数组索引的修改 和 length 变化
  • 不支持 Map/Set 等数据结构,只能代理普通对象和数组
  • 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题

响应式更新

  • this.$set():数组则调用 splice 方法,对象则调用 defineReactive 方法
  • this.$forceUpdate():直接调用渲染 Watcher 的 update 方法,绕过响应式系统的依赖收集机制,强制触发组件的重新渲染流程
  • Object.assign():重新赋新值,触发响应式更新

异步更新机制

数据在发生变化的时候,Vue 并不会立刻去更新 Dom,而是将修改数据的操作放在了一个队列中,在下一个事件循环”tick”中,Vue 才会刷新队列并执行实际的 DOM 更新
即:同一事件循环里对多个数据做修改,Vue 会把它们合并成一次 DOM 更新(避免频繁重排)

nextTick

作用

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

为什么有 nextTick?

由于上面的异步更新机制,因此改完数据立即读 DOM 仍然是旧的

原理

nextTick 内部维护一个数组,每次调用 nextTick 时会把回调函数压入这个数组,然后采用异步任务进行包装(微任务优先,宏任务兜底),等待 DOM 更新完成之后,会将队列中的事件拿来进行处理

插槽(slot)

插槽本质上是一个对象:

slots = {
  default: function(...args) {},
  slot1: function(...args) {},
  slot2: function(...args) {},
}

每个属性分别对应插槽的名字,属性的值是一个返回 VNode 的函数

v-if v-show

区别:

  • v-show 隐藏则是为该元素添加 display: none,dom 元素依旧还在。v-if 显示隐藏是将 dom 元素整个添加或删除
  • v-if 切换时会触发组件的生命周期
  • v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销
  • 如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好

keep-alive

作用:是 Vue 提供的一个内置组件,用来缓存组件的状态,避免在切换组件时重新渲染和销毁,从而提高性能

原理:在内部维护了一个 key数组 和一个 vnode缓存对象,通过 cache 对象和 keys 数组维护缓存实例。其基本逻辑是判断当前渲染的 vnode 是否有对应的缓存,如果有,会从缓存中读取到对应的组件实例,如果没有就会把它缓存。当缓存的数量超过 max 设置的数值时,keep-alive 会移除 key数组 中的第一个元素

// keep-alive 组件内部声明
created () {
  this.cache = Object.create(null)
  this.keys = []
},

跟 keep-alive 相关的生命周期有两个:activateddeactivated

异步组件的实现原理

异步组件实现的本质是 2 次渲染,第一次渲染生成一个占位的注释节点,等组件异步加载完毕后,再重新渲染,就会调用 forceUpdate 方法更新组件

组件之间的通信方式

方式优点缺点
props$emit常用只能父子
$refs$children偶尔用父子组件耦合
$parent$root基本不用父子组件严重耦合
provideinject跨层级传数据方便不清楚数据来源与变更
$attrs$listeners数据透传中间组件多余代码
vuex数据集中管理,可追溯代码比较复杂
EventBus各种不同组件间通信很方便,代码简洁事件多了后,难以对事件进行维护
localStoragesessionStorage简单,浏览器自带数据和状态比较混乱,不容易维护

常用修饰符

  • .trim:过滤首尾空格
  • .stop:阻止事件冒泡
  • .prevent:阻止事件默认行为
  • .sync:双向绑定
  • .lazy:输入框失焦时才会更新 v-model 的值
  • .number:将 v-medol 绑定的值转为数字

v-model 和 .sync

vue2.0中.sync与v-model - 掘金

vue-router

分为 hash 模式 和 history 模式

  • hash:通过监听 hashchange 事件
  • history:依赖于 HTML5 的 pushState 和 replaceState(需要 nginx 添加 try_files 解决 404 问题)

Vue 跟 React 有什么异同

相同点

  • 都是基于组件化开发
  • 都推荐单向数据流
  • 都使用了 虚拟 DOM 技术
  • 都支持 SSR

不同点

视图:

  • Vue: template
  • React: JSX

数据改变:

  • Vue: 响应式
  • React: 手动 setState

事件绑定:

  • Vue: 双向绑定
  • React: 单向绑定

路由、状态管理:各自的库