图解 Vue 响应式原理

这篇具有很好参考价值的文章主要介绍了图解 Vue 响应式原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  • Vue 初始化
  • 模板渲染
  • 组件渲染

为了便于理解,本文将从以下两个方面进行探索:

  • 从 Vue 初始化,到首次渲染生成 DOM 的流程。

  • 从 Vue 数据修改,到页面更新 DOM 的流程。

Vue 初始化

先从最简单的一段 Vue 代码开始:
"""

<template>
  <div>
	{{ message }}
  </div>
</template>
<script>
new Vue({
  data() {
	return {
	  message: "hello world",
	};
  },
});
</script>

"""


这段代码很简单,最终会在页面上打印一个 hello world,它是如何实现的呢?

我们从源头:new Vue 的地方开始分析。
"""

// 执行 new Vue 时会依次执行以下方法
// 1. Vue.prototype._init(option)
// 2. initState(vm)
// 3. observe(vm._data)
// 4. new Observer(data)

// 5. 调用 walk 方法,遍历 data 中的每一个属性,监听数据的变化。
function walk(obj) {
  const keys = Object.keys(obj);
  for (let i = 0; i < keys.length; i++) {
	defineReactive(obj, keys[i]);
  }
}

// 6. 执行 defineProperty 监听数据读取和设置。
function defineReactive(obj, key, val) {
  // 为每个属性创建 Dep(依赖搜集的容器,后文会讲)
  const dep = new Dep();
  // 绑定 get、set
  Object.defineProperty(obj, key, {
	get() {
	  const value = val;
	  // 如果有 target 标识,则进行依赖搜集
	  if (Dep.target) {
		dep.depend();
	  }
	  return value;
	},
	set(newVal) {
	  val = newVal;
	  // 修改数据时,通知页面重新渲染
	  dep.notify();
	},
  });
}

"""
数据描述符绑定完成后,我们就能得到以下的流程图:


图解 Vue 响应式原理


图中我们可以看到,Vue 初始化时,进行了数据的 get、set 绑定,并创建了一个 Dep 对象。

对于数据的 get、set 绑定我们并不陌生,但是 Dep 对象什么呢?

Dep 对象用于依赖收集,它实现了一个发布订阅模式,完成了数据 Data 和渲染视图 Watcher 的订阅,我们一起来剖析一下。
"""

class Dep {
  // 根据 ts 类型提示,我们可以得出 Dep.target 是一个 Watcher 类型。
  static target: ?Watcher;
  // subs 存放搜集到的 Watcher 对象集合
  subs: Array<Watcher>;
  constructor() {
	this.subs = [];
  }
  addSub(sub: Watcher) {
	// 搜集所有使用到这个 data 的 Watcher 对象。
	this.subs.push(sub);
  }
  depend() {
	if (Dep.target) {
	  // 搜集依赖,最终会调用上面的 addSub 方法
	  Dep.target.addDep(this);
	}
  }
  notify() {
	const subs = this.subs.slice();
	for (let i = 0, l = subs.length; i < l; i++) {
	  // 调用对应的 Watcher,更新视图
	  subs[i].update();
	}
  }
}

"""
根据对 Dep 的源码分析,我们得到了下面这张逻辑图:


图解 Vue 响应式原理


了解 Data 和 Dep 之后,我们来继续揭开 Watcher 的面纱。
"""

class Watcher {
  constructor(vm: Component, expOrFn: string | Function) {
	// 将 vm._render 方法赋值给 getter。
	// 这里的 expOrFn 其实就是 vm._render,后文会讲到。
	this.getter = expOrFn;
	this.value = this.get();
  }
  get() {
	// 给 Dep.target 赋值为当前 Watcher 对象
	Dep.target = this;
	// this.getter 其实就是 vm._render
	// vm._render 用来生成虚拟 dom、执行 dom-diff、更新真实 dom。
	const value = this.getter.call(this.vm, this.vm);
	return value;
  }
  addDep(dep: Dep) {
	// 将当前的 Watcher 添加到 Dep 收集池中
	dep.addSub(this);
  }
  update() {
	// 开启异步队列,批量更新 Watcher
	queueWatcher(this);
  }
  run() {
	// 和初始化一样,会调用 get 方法,更新视图
	const value = this.get();
  }
}

"""


源码中我们看到,Watcher 实现了渲染方法 _render

和 Dep 的关联, 初始化 Watcher 的时候,打上

Dep.target 标识,然后调用 get 方法进行页面渲

染。加上上文的 Data,目前 Data、Dep、Watcher

三者的关系如下:


图解 Vue 响应式原理


我们再拉通串一下整个流程:

Vue 通过defineProperty 完成了 Data 中所有数据

的代理,当数据触发 get 查询时,会将当前的

Watcher 对象加入到依赖收集池 Dep 中,当数据

Data 变化时,会触发 set 通知所有使用到这个 Data

的 Watcher 对象去 update 视图。

目前的整体流程如下:


图解 Vue 响应式原理


上图的流程中 Data 和 Dep 都是 Vue 初始化时创建的,但现在我们并不知道 Wacher 是从哪里创建的,带着这个问题,我们接着往下探索。

模板渲染

上文中,我们分析了初始化 Vue 过程中处理数据的

部分,接下来,我们分析一下数据渲染的部分。

其实 new Vue 执行到最后,会调用 mount 方法,

将 Vue 实例渲染成 dom 。
"""

// new Vue 执行流程。
// 1. Vue.prototype._init(option)
// 2. vm.$mount(vm.$options.el)
// 3. render = compileToFunctions(template) ,编译 Vue 中的 template 模板,生成 render 方法。
// 4. Vue.prototype.$mount 调用上面的 render 方法挂载 dom。
// 5. mountComponent

// 6. 创建 Watcher 实例
const updateComponent = () => {
  vm._update(vm._render());
};
// 结合上文,我们就能得出,updateComponent 就是传入 Watcher 内部的 getter 方法。
new Watcher(vm, updateComponent);

// 7. new Watcher 会执行 Watcher.get 方法
// 8. Watcher.get 会执行 this.getter.call(vm, vm) ,也就是执行 updateComponent 方法
// 9. updateComponent 会执行 vm._update(vm._render())

// 10. 调用 vm._render 生成虚拟 dom
Vue.prototype._render = function (): VNode {
  const vm: Component = this;
  const { render } = vm.$options;
  let vnode = render.call(vm._renderProxy, vm.$createElement);
  return vnode;
};
// 11. 调用 vm._update(vnode) 渲染虚拟 dom
Vue.prototype._update = function (vnode: VNode) {
  const vm: Component = this;
  if (!prevVnode) {
	// 初次渲染
	vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false);
  } else {
	// 更新
	vm.$el = vm.__patch__(prevVnode, vnode);
  }
};
// 12. vm.__patch__ 方法就是做的 dom diff 比较,然后更新 dom,这里就不展开了。

"""
看完 Vue 模板渲染的过程,我们可以得到如下的流程图:


图解 Vue 响应式原理


到这里,我们就知道了 Watcher 其实是在 Vue 初始

化的阶段创建的,属于生命周期中 beforeMount 的

位置创建的,创建 Watcher 时会执行 render 方法,

最终将 Vue 代码渲染成真实的 DOM。

我们再将之前的流程整合一下,就能得到以下的流程:

图解 Vue 响应式原理


上图分析了 Vue 初始化到渲染 DOM 的整个过程,最后我们再分析一

下,当数据变化时,Vue 又是怎么进行更新的?

其实,在上图也能看出,在 Data 变化时,会调用 Dep.notify 方法,随

即调用 Watcher 内部的 update 方法,此方法会将所有使用到这个 Data

的 Watcher 加入一个队列,并开启一个异步队列进行更新,最终执行

_render 方法完成页面更新。

整体的流程如下:


图解 Vue 响应式原理


好了,探索到这里,Vue 的响应式原理,已经被我们分析透彻了,如果你

还没有明白,不妨再细品一下上图。

组件渲染

本来探索到上面的流程图就结束了,但好奇的我又想到了一个问题 😂

Vue 组件又是怎么渲染的呢?

带着这个问题,我继续查阅了源码。
"""

// 从模板编译开始,当发现一个自定义组件时,会执行以下函数
// 1. compileToFunctions(template)
// 2. compile(template, options);
// 3. const ast = parse(template.trim(), options)
// 4. const code = generate(ast, options)
// 5. createElement

// 6. createComponent
export function createComponent(
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  // $options._base 其实就是全局 Vue 构造函数,在初始化时 initGlobalAPI 中定义的:Vue.options._base = Vue
  const baseCtor = context.$options._base;
  // Ctor 就是 Vue 组件中 <script> 标签下 export 出的对象
  if (isObject(Ctor)) {
	// 将组件中 export 出的对象,继承自 Vue,得到一个构造函数
	// 相当于 Vue.extend(YourComponent)
	Ctor = baseCtor.extend(Ctor);
  }
  const vnode = new VNode(`vue-component-${Ctor.cid}xxx`);
  return vnode;
}

// 7. 实现组件继承 Vue,并调用 Vue._init 方法,进行初始化
Vue.extend = function (extendOptions: Object): Function {
  const Super = this;
  const Sub = function VueComponent(options) {
	// 调用 Vue.prototype._init,之后的流程就和首次加载保持一致
	this._init(options);
  };
  // 原型继承,相当于:Component extends Vue
  Sub.prototype = Object.create(Super.prototype);
  Sub.prototype.constructor = Sub;
  return Sub;
};

"""


看完组件渲染的源码后,结合上文,重新整理了一张流程图,图中的蓝色

部分就是渲染组件的过程。


图解 Vue 响应式原理



好了,现在是真的结束了,最终的流程图就是上面的这一张图。

问个问题,现在你理解 Vue 响应式原理了吗?

如果仍觉得不好理解,我这里还准备了一张带标注的简图 😂


图解 Vue 响应式原理


思考与总结

本文从源码的角度,介绍了 Vue 响应式原理,来简单回顾一下吧。

  • 从 new Vue 开始,首先通过 get、set 监听 Data 中的数据变化,同时创建 Dep 用来搜集使用该 Data 的 Watcher。

  • 编译模板,创建 Watcher,并将 Dep.target 标识为当前 Watcher。

  • 编译模板时,如果使用到了 Data 中的数据,就会触发 Data 的 get 方法,然后调用 Dep.addSub 将 Watcher 搜集起来。

  • 数据更新时,会触发 Data 的 set 方法,然后调用 Dep.notify 通知所有使用到该 Data 的 Watcher 去更新 DOM。文章来源地址https://www.toymoban.com/news/detail-597708.html

到了这里,关于图解 Vue 响应式原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入理解Vue的响应式原理

    用过Vue这个框架的人应该都知道,数据驱动是Vue框架的核心,数据双向绑定是它的一大特色,根据官方的解释,我们可以比较清晰地去知道响应式的简单原理。 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.definePro

    2023年04月17日
    浏览(21)
  • Vue源码学习 - 数据响应式原理

    Vue.Js的核心包括一套 “响应式系统”。“响应式”,是指当数据改变后,Vue会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。 vue2.X的数据响应式是利用 Object.defineProperty() 实现的,通过定义对象属性 getter/setter 拦截对属性的获取和

    2024年02月15日
    浏览(26)
  • Vue3 数据响应式原理

    核心: 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等… 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作 Vue3的响应式比Vue2好在哪里? 效率更高了,Vue2中假设监听某个对象,该对象中有一万个属性,他要循

    2024年02月11日
    浏览(40)
  • 手写Vue3响应式数据原理

    我们想要对一个对象数据进行处理,从而实现更改dom。但如何更改对一个对象数据进行更改呢? vue2 的双向数据绑定是利⽤ES5 的⼀个 API ,Object.defineProperty()对数据进⾏劫持 结合 发布订阅模式的⽅式来实现的。 vue3 中使⽤了 ES6 的 ProxyAPI 对数据代理,通过 reactive() 函数给每⼀

    2024年02月11日
    浏览(37)
  • Vue.js 2.0 深入响应式原理

    大部分的基础内容我们已经讲到了,现在讲点底层内容。Vue 最显著的一个功能是响应系统 —— 模型只是普通对象,修改它则更新视图。这会让状态管理变得非常简单且直观,不过理解它的原理以避免一些常见的陷阱也是很重要的。在本节中,我们将开始深挖 Vue 响应系统的

    2023年04月11日
    浏览(32)
  • 学习Vue:响应式原理与性能优化策略

    性能优化是Vue.js应用开发中的一个关键方面,而深入了解响应式原理并采用有效的性能优化策略可以显著提升应用的性能。本文将解释响应式原理并介绍一些性能优化策略,旨在帮助您构建高性能的Vue.js应用。 Vue.js的响应式原理是通过利用 Object.defineProperty 或 Proxy 来追踪数据

    2024年02月11日
    浏览(26)
  • Vue3.0中的响应式原理

    实现原理: - 对象类型:通过 ``Object.defineProperty()``对属性的读取、修改进行拦截(数据劫持)。 - 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。 存在问题: - 新增属性、删除属性, 界面不会更新。 - 直接通过下标修改数组, 界面

    2023年04月17日
    浏览(34)
  • vue2响应式原理----发布订阅模式

    很多人感觉vue2的响应式其实用到了观察者+发布订阅。我们先来看一下简单的发布订阅的代码: 从上面中发现一个重要的点,发布者和订阅者是根据key值来区分的,然后通过消息中心来中转的,他们家是是实现不知道对方是谁。 而观察者模式中观察者是一开始就知道自己观察

    2024年04月14日
    浏览(29)
  • Vue2.0 的响应式原理 私

    使用的Object.defineProperty()重新定义对象,给data的每个属性都添加了getter和setter方法。这时候会为对象的每个属性创建一个Dep实例  (依赖)。Dep实例可以订阅和通知相关的Watcher实例。,  这一步叫  数据劫持  或者 依赖收集 在数据发生更新后调用 set 时会通知发布者 notify

    2024年02月11日
    浏览(27)
  • 【Vue3响应式原理#01】Reactivity

    专栏分享:vue2源码专栏,vue3源码专栏,vue router源码专栏,玩具项目专栏,硬核💪推荐🙌 欢迎各位ITer关注点赞收藏🌸🌸🌸 以下是柏成根据Vue3官方课程整理的响应式书面文档 - 第一节,课程链接在此:Vue 3 Reactivity - Vue 3 Reactivity | Vue Mastery,本文档可作为课程的辅助材料,

    2024年02月08日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包