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 进行唯一标识,在进行数据变化时可以不用整个数据重新渲染

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

响应式更新

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

nextTick

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

原理

数据在发生变化的时候,Vue 并不会立刻去更新 Dom,而是将修改数据的操作放在了一个微任务队列中。等待当前任务完成之后,会将队列中的事件拿来进行处理,进行 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 - 掘金

组件库按需加载原理

通过 babel-plugin-import 插件在编译时通过 AST 转换代码

Vue 跟 React 有什么异同

相同点

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

不同点

视图:

  • Vue: template
  • React: JSX

数据改变:

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

事件绑定:

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

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