热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何使用Vue实现Observer

这次给大家带来如何使用Vue实现Observer,使用Vue实现Observer的注意事项有哪些,下面就是实战案例,一起来看一下。导语:本文是对 Vue 官方文档深入响应式


这次给大家带来如何使用Vue实现Observer,使用Vue实现Observer的注意事项有哪些,下面就是实战案例,一起来看一下。

导语:

本文是对 Vue 官方文档深入响应式原理(https://cn.vuejs.org/v2/guide/reactivity.html)的理解,并通过源码还原实现过程。

响应式原理可分为两步,依赖收集的过程与触发-重新渲染的过程。依赖收集的过程,有三个很重要的类,分别是 Watcher、Dep、Observer。本文主要解读 Observer 。

这篇文章讲解上篇文章没有覆盖到的 Observer 部分的内容,还是先看官网这张图:

如何使用Vue实现Observer - 文章图片

Observer 最主要的作用就是实现了上图中touch -Data(getter) - Collect as Dependency这段过程,也就是依赖收集的过程。

还是以下面的代码为例子进行梳理:

(注:左右滑动即可查看完整代码,下同)

varvm = newVue({
el: '#demo',
data: {
firstName: 'Hello',
fullName: ''
},
watch: {
firstName(val) {
this.fullName = val + 'TalkingData';
},
}
})

在源码中,通过还原Vue 进行实例化的过程,从开始一步一步到Observer 类的源码依次为(省略了很多不在本篇文章讨论的代码):

// src/core/instance/index.js
functionVue(options) {
if(process.env.NODE_ENV !== 'production'&&
!(thisinstanceofVue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
// src/core/instance/init.js
Vue.prototype._init = function(options?: Object) {
constvm: CompOnent= this
// ...
initState(vm)
// ...
}
// src/core/instance/state.js
exportfunctioninitState(vm: Component) {
// ...
cOnstopts= vm.$options
if(opts.data) {
initData(vm)
}
// ...
}
functioninitData(vm: Component) {
letdata = vm.$options.data
data = vm._data = typeofdata === 'function'
? getData(data, vm)
: data || {}
// ...
// observe data
observe(data, true/* asRootData */)
}

在initData 方法中,开始了对data 项中的数据进行“观察”,会将所有数据的变成observable 的。接下来看observe 方法的代码:

// src/core/observer/index.js
functionobserve(value: any, asRootData: ?boolean): Observer| void{
// 如果不是对象,直接返回
if(!isObject(value) || value instanceofVNode) {
return
}
letob: Observer | void
if(hasOwn(value, 'ob') && value.ob instanceofObserver) {
// 如果有实例则返回实例
ob = value.ob
} elseif(
// 确保value是单纯的对象,而不是函数或者是Regexp等情况
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 实例化一个 Observer
ob = newObserver(value)
}
if(asRootData && ob) {
ob.vmCount++
}
returnob
}

observe 方法的作用是给data 创建一个Observer 实例并返回,如果data 有ob属性了,说明已经有Observer 实例了,则返回现有的实例。Vue 的响应式数据都会有一个ob的属性,里面存放了该属性的Observer 实例,防止重复绑定。再来看new Observer(value) 过程中发生了什么:

exportclassObserver{
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor(value: any) {
this.value = value
this.dep = newDep()
this.vmCount = 0
def(value, 'ob', this)
if(Array.isArray(value)) {
// ...
this.observeArray(value)
} else{
this.walk(value)
}
}
walk (obj: Object) {
cOnstkeys= Object.keys(obj)
for(leti = 0; i defineReactive(obj, keys[i], obj[keys[i]])
}
}
observeArray (items: Array) {
for(leti = 0, l = items.length; i observe(items[i])
}
}
}

通过源码可以看到,实例化Observer 过程中主要是做了两个判断。如果是数组,则对数组里面的每一项再次调用oberser 方法进行观察;如果是非数组的对象,遍历对象的每一个属性,对其调用defineReactive 方法。这里的defineReactive 方法就是核心!通过使用Object.defineProperty 方法对每一个需要被观察的属性添加get/set,完成依赖收集。依赖收集过后,每个属性都会有一个Dep 来保存所有Watcher 对象。按照文章最开始的例子来讲,就是对firstName和fullName分别添加了get/set,并且它们各自有一个Dep 实例来保存各自观察它们的所有Watcher 对象。下面是defineReactive 的源码:

exportfunctiondefineReactive(
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
cOnstdep= newDep()
// 获取属性的自身描述符
cOnstproperty= Object.getOwnPropertyDeor(obj, key)
if(property && property.cOnfigurable=== false) {
return
}
// cater for pre-defined getter/setters
// 检查属性之前是否设置了 getter/setter
// 如果设置了,则在之后的 get/set 方法中执行设置了的 getter/setter
cOnstgetter= property && property.get
cOnstsetter= property && property.set
// 通过对属性再次调用 observe 方法来判断是否有子对象
// 如果有子对象,对子对象也进行依赖搜集
letchildOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: functionreactiveGetter() {
// 如果属性原本拥有getter方法则执行
cOnstvalue= getter ? getter.call(obj) : val
if(Dep.target) {
// 进行依赖收集
dep.depend()
if(childOb) {
// 如果有子对象,对子对象也进行依赖搜集
childOb.dep.depend()
// 如果属性是数组,则对每一个项都进行依赖收集
// 如果某一项还是数组,则递归
if(Array.isArray(value)) {
dependArray(value)
}
}
}
returnvalue
},
set: functionreactiveSetter(newVal) {
// 如果属性原本拥有getter方法则执行
// 通过getter方法获取当前值,与新值进行比较
// 如果新旧值一样则不需要执行下面的操作
cOnstvalue= getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if(newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if(process.env.NODE_ENV !== 'production'&& customSetter) {
customSetter()
}
if(setter) {
// 如果属性原本拥有setter方法则执行
setter.call(obj, newVal)
} else{
// 如果原本没有setter则直接赋新值
val = newVal
}
// 判断新的值是否有子对象,有的话继续观察子对象
childOb = !shallow && observe(newVal)
// 通知所有的观察者,更新状态
dep.notify()
}
})
}

按照源码中的中文注释,应该可以明白defineReactive 执行的过程中做了哪些工作。其实整个过程就是递归,为每个属性添加getter/setter。对于getter/setter,同样也需要对每一个属性进行递归(判断子对象)的完成观察者模式。对于getter,用来完成依赖收集,即源码中的dep.depend()。对于setter,一旦一个数据触发其set方法,便会发布更新消息,通知这个数据的所有观察者也要发生改变。即源码中的dep.notify()。

相信看了本文案例你已经掌握了方法,更多精彩请关注其它相关文章!

推荐阅读:

怎样使用vue文件树组件

Javascript EventEmitter底层逻辑剖析

以上就是如何使用Vue实现Observer的详细内容,更多请关注其它相关文章!



推荐阅读
  • 本文详细介绍了如何在Kendo UI for jQuery的数据管理组件中,将行标题字段呈现为锚点(即可点击链接),帮助开发人员更高效地实现这一功能。通过具体的代码示例和解释,即使是新手也能轻松掌握。 ... [详细]
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 解决Anaconda安装TensorFlow时遇到的TensorBoard版本问题
    本文介绍了在使用Anaconda安装TensorFlow时遇到的“Could not find a version that satisfies the requirement tensorboard”错误,并提供详细的解决方案,包括创建虚拟环境和配置PyCharm项目。 ... [详细]
  • JSOI2010 蔬菜庆典:树结构中的无限大权值问题
    本文探讨了 JSOI2010 的蔬菜庆典问题,主要关注如何处理非根非叶子节点的无限大权值情况。通过分析根节点及其子树的特性,提出了有效的解决方案,并详细解释了算法的实现过程。 ... [详细]
  • 本文总结了优化代码可读性的核心原则与技巧,通过合理的变量命名、函数和对象的结构化组织,以及遵循一致性等方法,帮助开发者编写更易读、维护性更高的代码。 ... [详细]
  • Python 学习是否需要先掌握 C 语言?
    Python 是一门非常适合编程入门的语言,很多人疑惑是否需要先学习 C 语言才能更好地掌握 Python。本文将详细探讨这个问题,并为初学者提供专业的建议。 ... [详细]
  • 本文详细探讨了HTML表单中GET和POST请求的区别,包括它们的工作原理、数据传输方式、安全性及适用场景。同时,通过实例展示了如何在Servlet中处理这两种请求。 ... [详细]
  • 本文探讨了在使用Selenium进行自动化测试时,由于webdriver对象实例化位置不同而导致浏览器闪退的问题,并提供了详细的代码示例和解决方案。 ... [详细]
  • 算法题解析:最短无序连续子数组
    本题探讨如何通过单调栈的方法,找到一个数组中最短的需要排序的连续子数组。通过正向和反向遍历,分别使用单调递增栈和单调递减栈来确定边界索引,从而定位出最小的无序子数组。 ... [详细]
  • 本文旨在提供一套高效的面试方法,帮助企业在短时间内找到合适的产品经理。虽然观点较为直接,但其方法已被实践证明有效,尤其适用于初创公司和新项目的需求。 ... [详细]
  • 反向投影技术主要用于在大型输入图像中定位特定的小型模板图像。通过直方图对比,它能够识别出最匹配的区域或点,从而确定模板图像在输入图像中的位置。 ... [详细]
  • 如何将本地Docker镜像推送到阿里云容器镜像服务
    本文详细介绍将本地Docker镜像上传至阿里云容器镜像服务的步骤,包括登录、查看镜像列表、推送镜像以及确认上传结果。通过本文,您将掌握如何高效地管理Docker镜像并将其存储在阿里云的镜像仓库中。 ... [详细]
  • Python处理Word文档的高效技巧
    本文详细介绍了如何使用Python处理Word文档,涵盖从基础操作到高级功能的各种技巧。我们将探讨如何生成文档、定义样式、提取表格数据以及处理超链接和图片等内容。 ... [详细]
  • 基于JQuery实现的评分插件
    本文介绍了一个使用JQuery创建的交互式评分控件。当用户将鼠标悬停在星星上时,左侧的星星会变为实心,右侧保持空心,并显示对应的评分等级;移开鼠标后,所有星星恢复为空心状态。 ... [详细]
  • 本文介绍了如何利用 Spring Boot 和 Groovy 构建一个灵活且可扩展的动态计算引擎,以满足钱包应用中类似余额宝功能的推广需求。我们将探讨不同的设计方案,并最终选择最适合的技术栈来实现这一目标。 ... [详细]
author-avatar
笃志单车小博_801
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有