如何使用Vue实现Observer

这次给大家带来如何使用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.jsfunctionVue(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.jsVue.prototype._init = function(options?: Object) {constvm: Component = this// ...initState(vm)// ...}// src/core/instance/state.jsexportfunctioninitState(vm: Component) {// ...constopts = vm.$optionsif(opts.data) {initData(vm)}// ...}functioninitData(vm: Component) {letdata = vm.$options.datadata = vm._data = typeofdata === 'function'? getData(data, vm): data || {}// ...// observe dataobserve(data, true/* asRootData */)}

登录后复制

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

// src/core/observer/index.jsfunctionobserve(value: any, asRootData: ?boolean): Observer| void{// 如果不是对象,直接返回if(!isObject(value) || value instanceofVNode) {return}letob: Observer | voidif(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) {// 实例化一个 Observerob = 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 $dataconstructor(value: any) {this.value = valuethis.dep = newDep()this.vmCount = 0def(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 < keys.length; i++) {defineReactive(obj, keys[i], obj[keys[i]])}}observeArray (items: Array) {for(leti = 0, l = items.length; i < l; 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/setterconstgetter = property && property.getconstsetter = property && property.set// 通过对属性再次调用 observe 方法来判断是否有子对象// 如果有子对象,对子对象也进行依赖搜集letchildOb = !shallow && observe(val)Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: functionreactiveGetter() {// 如果属性原本拥有getter方法则执行constvalue = getter ? getter.call(obj) : valif(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的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2756582.html

(0)
上一篇 2025年3月8日 07:40:37
下一篇 2025年2月27日 20:00:11

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 怎样使用vue实现全选反选

    这次给大家带来怎样使用vue实现全选反选,使用vue实现全选反选的注意事项有哪些,下面就是实战案例,一起来看一下。 全选功能可以说是前端开发中非常常见的一个功能,以前的项目开发用jQuery开发比较多。最近在使用vue前端框架重构之前的项目…

    2025年3月8日
    200
  • 如何操作vue2实现购物车和地址选配

    这次给大家带来如何操作vue2实现购物车和地址选配,操作vue2实现购物车和地址选配的注意事项有哪些,下面就是实战案例,一起来看一下。 首先,vue基础js写法 new Vue({ el:”#app”, //模型 data:{ }, fil…

    编程技术 2025年3月8日
    200
  • 如何使用Koa2文件上传下载

    这次给大家带来如何使用Koa2文件上传下载,使用Koa2文件上传下载的注意事项有哪些,下面就是实战案例,一起来看一下。 前言 上传下载在 web 应用中还是比较常见的,无论是图片还是其他文件等。在 Koa 中,有很多中间件可以帮助我们快速的…

    编程技术 2025年3月8日
    200
  • 怎么使用spirngmvc js传递复杂json参数到controller

    这次给大家带来怎么使用spirngmvc js传递复杂json参数到controller,使用spirngmvc js传递复杂json参数到controller的注意事项有哪些,下面就是实战案例,一起来看一下。 Spring MVC在接收集…

    编程技术 2025年3月8日
    200
  • 如何使用js内el表达式与非空判断

    这次给大家带来如何使用js内el表达式与非空判断,使用js内el表达式与非空判断的注意事项有哪些,下面就是实战案例,一起来看一下。 页面跳转后,使用spring mvc向前端页面传过来一个json对象,要在js中获取后,做处理。 返回的js…

    编程技术 2025年3月8日
    200
  • vue-cli开发环境实现跨域请求的方法

    本篇文章主要介绍了vue-cli开发环境实现跨域请求的方法,现在分享给大家,也给大家做个参考。 前端开发时,请求后台接口经常需要跨域,vue-cli实现跨域请求只需要打开config/index.js,修改如下内容即可。 //例如要请求的接…

    编程技术 2025年3月8日
    200
  • 怎样正确使用vue组件复用功能

    这次给大家带来怎样正确使用vue组件复用功能,正确使用vue组件复用功能的注意事项有哪些,下面就是实战案例,一起来看一下。 一、什么是组件 组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装…

    编程技术 2025年3月8日
    200
  • 从零开始使用React Router v4

    这次给大家带来从零开始使用React Router v4,从零开始使用React Router v4的注意事项有哪些,下面就是实战案例,一起来看一下。 江湖传言,目前官方同时维护 2.x 和 4.x 两个版本。(ヾ(。ꏿ﹏ꏿ)ノ゙咦,此刻相…

    2025年3月8日
    200
  • 怎样正确使用vuex项目结构目录与配置

    这次给大家带来怎样正确使用vuex项目结构目录与配置,使用vuex项目结构目录与配置的注意事项有哪些,下面就是实战案例,一起来看一下。 首先先正经的来一段官网的”忠告”: vuex需要遵守的规则: 一、应用层级的状态…

    2025年3月8日
    200
  • 怎样使用JS求Number类型数组最大元素

    这次给大家带来怎样使用JS求Number类型数组最大元素,使用JS求Number类型数组最大元素的注意事项有哪些,下面就是实战案例,一起来看一下。 以下介绍四个方法。 1. 不使用任何库函数 代码如下: function findMax1 …

    编程技术 2025年3月8日
    200

发表回复

登录后才能评论