精华内容
下载资源
问答
  • 1.5 响应式系统 1.5.1 响应式宣言 关注“响应式”的朋友不难搜索到关于“响应式宣言”的介绍,先上图: 这张图凝聚了许多大神的智慧和经验,见官网,中文版官网,如果你认可这个宣言的内容,还可以签下你的...

    本系列其他文章见:《响应式Spring的道法术器》
    前情提要:响应式编程 | 响应式流

    1.5 响应式系统

    1.5.1 响应式宣言

    关注“响应式”的朋友不难搜索到关于“响应式宣言”的介绍,先上图:

    这张图凝聚了许多大神的智慧和经验,见官网中文版官网,如果你认可这个宣言的内容,还可以签下你的大名。虽然这些内容多概念而少实战,让人感觉是看教科书,但是字字千金,不时看一看都会有新的体会和收获。

    这也是新时代男朋友的行为准则:

    • Responsive,要及时响应,24小时在线,不准不接电话,微信回复时间要在5分钟以内;
    • 如何做到Responsive呢,首先要Resilient,就是无论如何要有回应。即使在玩LOL,也要接电话,哪怕正在联合国演讲,那么也要设置好“对不起宝贝儿,稍后打给你么么哒~”的自动回复;
    • 做到Responsive的另一点是Elastic,要弹性应对大量命令的到来。当奉天承运的圣旨过多时完不成怎么办?对不起,不存在的,如果学不会三头六臂,那就拉几个好友帮忙;
    • 圣旨是通过异步方式传递的(Message Driven),给花店打电话订花、到蛋糕店订蛋糕等,别忘了购物车里的“消息队列”要及时处理喽~ 这些订单都是具有一定格式和目的地的消息,然后异步等待快递上门。

    以响应式系统方式构建的系统更加灵活,松耦合和可扩展。这使得它们更容易开发,而且更加拥抱变化。及时地响应,以保证良好的用户体验。系统错误和异常在所难免,当异常出现时,要优雅地处理之,不要任其蔓延、甚至到达用户眼前。

    关于宣言的具体内容官网上很详细,就不多赘述了。

    从落地方面,我们不难想到一些具体技术来支撑响应式宣言的目标:

    • 比如如今比较火热的云原生和DevOps的理念与实践,以及更早一些的自动化运维,都有助于让系统更加Elastic,不过系统架构的微服务化也是功不可没;
    • 类似于Hystrix的熔断器(Circuit Breaker)使得系统更加Resilient,因为它能够及时将服务异常遏制在可控范围内,避免雪崩;而类似kubernetes的云原生应用能够及时发现和重建系统中的异常服务;
    • 而类似RabbitMQ、ActiveMQ这样的消息队列产品有助于构建消息驱动的系统,并发挥解耦、提速、广播、削峰的作用;
    • 消息驱动有利于系统的弹性和可靠性,弹性和可靠性又使得系统的响应更加及时;
    • 等等。

    1.5.2 响应式编程与响应式系统

    响应式宣言是一组架构与设计原则,符合这些原则的系统可以认为是响应式的系统。而响应式系统与响应式编程是不同层面的内容。

    看到网上有些文章在介绍RxJava、Reactor等响应式编程技术的时候,会用响应式宣言来引出话题,稍微有点驴唇对马嘴的感觉(_)。响应式系统(或响应式宣言)与响应式编程又是一对容易被揉在一起用的两个术语(上一对容易被混用的术语是“响应式编程RP”和“函数响应式编程FRP”,见1.3.1 lambda与函数式),它们从关系上看虽然不如“雷锋”和“雷峰塔”、Java和JavaScript那么远,但并不存在必然的因果关系。

    作为《响应式宣言》的作者,Jonas Bonér和Viktor Klang解释了响应式编程与响应式系统的区别与联系(“Linux中国”上有翻译版),其中也有对响应式宣言四个原则的解读,值得学习。我从中总结了一些二者的不同点:

    1)战术与战略的区别

    响应式编程是异步编程下的一个子集,是一种范式,有具体的开发库,侧重于由信息/数据流而不是命令式的控制流来推动逻辑的前进。

    响应式宣言是一组设计原则,一种关于分布式环境下系统架构与设计的思考方式,响应式系统是符合这一架构风格的系统。

    2)事件驱动与消息驱动的区别

    响应式编程——专注于短时间的数据流链条上的计算——因此倾向于事件驱动;而响应式系统——关注于通过分布式系统的通信和协作所得到的弹性和可靠性——则是消息驱动的。

    响应式宣言中从定义上阐述了消息驱动与事件驱动的不同

    一条消息就是一则被送往一个明确目的地的数据。一个事件则是达到某个给定状态的组件发出的一个信号。在一个消息驱动系统中,可寻址到的接收者等待消息的到来然后响应它,否则保持休眠状态。在一个事件驱动系统中,通知的监听者被绑定到消息源上,这样当消息被发出时它就会被调用。这意味着一个事件驱动系统专注于可寻址的事件源而消息驱动系统专注于可寻址的接收者。

    3)组件范围与系统范围的区别

    响应式编程的“活动范围”是在组件内的,是一种管理组件或服务内部逻辑和数据流的技术,即使像1.4.2节中那样数据流从服务B向服务A的流动,也并非跨服务的消息传递,只是基于API的调用而已。

    响应式系统,强调组件/服务间的信息交流,并通过响应式宣言提供了一种处理分布式系统弹性与可靠性的原则,因此是面向分布式的系统范围的。

    4)空间解耦能力的区别

    正如前边介绍到的异步调用方式那样,事件驱动的响应式编程侧重于时间上的解耦,从而在技术层面提供了一种更高性能的并发方式。然而其范围限定了它不易于实现空间上的解耦。

    消息驱动的异步性使得响应式系统既能够在时间上解耦,还具有空间的解耦能力。服务间不仅可以通过消息队列实现分布式协作,还可以根据负载实现单个服务的弹性伸缩,从而实现响应式宣言中Elastic的能力。

    5)总结

    响应式编程技术通常用于在单个节点或服务中对数据流进行异步非阻塞的处理。当有多个结点时,就需要认真考量数据一致性(data consistency)、跨结点沟通(cross-node communication)、协调(coordination)、版本控制(versioning)、编排(orchestration)、错误管理(failure management)、关注与责任(concerns and responsibilities)分离等等的内容——这些都是响应式系统架构要考虑的内容。

    类似的,Spring WebFlux是一种响应式编程框架,用于开发响应式应用,而Spring Cloud不仅是更是一套适应于当今云原生环境下微服务架构基础,更加接近响应式宣言的目标和响应式系统的设计原则。

    不过也应该看到,也正是由于响应式宣言中对现代系统的Responsive、Resilient、Elastic和Message Driven的要求,使得对响应式编程技术的呼声越来越高,显然响应式编程技术是构建响应式系统的合适工具之一。尤其是随着面向响应式宣言的**响应式流规范(Reactive Streams Specification)**这一顶层设计的提出,类似Reactor、RxJava、Vert.x、Spring WebFlux等的响应式编程技术在响应式系统中必将发挥越来越大的作用。

    关于响应式系统的话题比较大,涉及到许多的理念、技术。本系列的文章仍主要聚焦于响应式编程的范畴,并在最后讨论响应式编程在Spring Cloud中的应用。

    展开全文
  • 什么是响应式系统 一句话概括:数据变更驱动视图更新。这样我们就可以以“数据驱动”的思维来编写我们的代码,更多的关注业务,而不是dom操作。其实Vue响应式的实现是一个变化追踪和变化应用的过程。 vue响应式原理 ...
  • Vue最巧妙的特性之一是其响应式系统,而我们也能够在仓库的packages/reactivity模块下找到对应的实现。虽然源码的代码量不多,网上的分析文章也有一堆,但是要想清晰地理解响应式原理的具体实现过程,还是挺费脑筋的...
  • 响应式系统的基本原理 响应式系统 Vue.js 是一款 MVVM 框架,数据模型仅仅是普通的 JavaScript 对象,但是对这些对象进行操作时,却能影响对应视图,它的核心实现就是「响应式系统」。尽管我们在使用 Vue.js ...

    响应式系统的基本原理

    响应式系统

    Vue.js 是一款 MVVM 框架,数据模型仅仅是普通的 JavaScript 对象,但是对这些对象进行操作时,却能影响对应视图,它的核心实现就是「响应式系统」。尽管我们在使用 Vue.js 进行开发时不会直接修改「响应式系统」,但是理解它的实现有助于避开一些常见的「」,也有助于在遇见一些琢磨不透的问题时可以深入其原理来解决它。

    Object.defineProperty

    首先我们来介绍一下 Object.defineProperty,Vue.js就是基于它实现「响应式系统」的。

    首先是使用方法:

    /*
        obj: 目标对象
        prop: 需要操作的目标对象的属性名
        descriptor: 描述符
        
        return value 传入对象
    */
    Object.defineProperty(obj, prop, descriptor)
    
    

    descriptor的一些属性,简单介绍几个属性,具体可以参考 MDN 文档

    • enumerable,属性是否可枚举,默认 false。
    • configurable,属性是否可以被修改或者删除,默认 false。
    • get,获取属性的方法。
    • set,设置属性的方法。

    实现 observer(可观察的)

    知道了 Object.defineProperty 以后,我们来用它使对象变成可观察的。

    这一部分的内容我们在第二小节中已经初步介绍过,在 init 的阶段会进行初始化,对数据进行「响应式化」。

    为了便于理解,我们不考虑数组等复杂的情况,只对对象进行处理。

    首先我们定义一个 cb 函数,这个函数用来模拟视图更新,调用它即代表更新视图,内部可以是一些更新视图的方法。

    function cb (val) {
        /* 渲染视图 */
        console.log("视图更新啦~");
    }
    
    

    然后我们定义一个 defineReactive ,这个方法通过 Object.defineProperty 来实现对对象的「响应式」化,入参是一个 obj(需要绑定的对象)、key(obj的某一个属性),val(具体的值)。经过 defineReactive 处理以后,我们的 obj 的 key 属性在「读」的时候会触发 reactiveGetter 方法,而在该属性被「写」的时候则会触发 reactiveSetter 方法。

    function defineReactive (obj, key, val) {
        Object.defineProperty(obj, key, {
            enumerable: true,       /* 属性可枚举 */
            configurable: true,     /* 属性可被修改或删除 */
            get: function reactiveGetter () {
                return val;         /* 实际上会依赖收集,下一小节会讲 */
            },
            set: function reactiveSetter (newVal) {
                if (newVal === val) return;
                cb(newVal);
            }
        });
    }
    
    

    当然这是不够的,我们需要在上面再封装一层 observer 。这个函数传入一个 value(需要「响应式」化的对象),通过遍历所有属性的方式对该对象的每一个属性都通过 defineReactive 处理。(注:实际上 observer 会进行递归调用,为了便于理解去掉了递归的过程)

    function observer (value) {
        if (!value || (typeof value !== 'object')) {
            return;
        }
        
        Object.keys(value).forEach((key) => {
            defineReactive(value, key, value[key]);
        });
    }
    
    

    最后,让我们用 observer 来封装一个 Vue 吧!

    在 Vue 的构造函数中,对 options 的 data 进行处理,这里的 data 想必大家很熟悉,就是平时我们在写 Vue 项目时组件中的 data 属性(实际上是一个函数,这里当作一个对象来简单处理)。

    class Vue {
        /* Vue构造类 */
        constructor(options) {
            this._data = options.data;
            observer(this._data);
        }
    }
    
    

    这样我们只要 new 一个 Vue 对象,就会将 data 中的数据进行「响应式」化。如果我们对 data 的属性进行下面的操作,就会触发 cb 方法更新视图。

    let o = new Vue({
        data: {
            test: "I am test."
        }
    });
    o._data.test = "hello,world.";  /* 视图更新啦~ */
    
    

    至此,响应式原理已经介绍完了,接下来让我们学习「响应式系统」的另一部分 ——「依赖收集」。

    注:本节代码参考《响应式系统的基本原理》

    展开全文
  • Vue 3.0响应式系统编程概述

    千次阅读 2020-09-27 14:03:51
    Vue 3.0响应式系统简单实现响应式系统原理3.x响应式回顾核心方法proxy回顾源码实现reactive(只能转换对象)依赖收集effect、 tracktrigger 触发更新ref - 可以接受对象或者基础类型,如果是响应式对象直接返回,是...

    随着Vue3.0的正式发布,前端又多了一门需要学习的功课,本文主要是对vue3.0响应式系统的简单剖析及实现:

    一、 3.0响应式

    • 使用Proxy对象实现属性监听
    • 多层属性嵌套,在访问属性过程中处理下一级属性
    • 默认监听动态添加的属性
    • 默认监听属性的删除操作
    • 默认监听数组索引和length属性
    • 可以作为单独的模块使用

    二、 核心方法

    • reactive (将对象转换为响应式对象)
    • ref (将基本类型的值转为具有一个value属性的响应式对象)
    • toRefs (解构响应式对象数据)
    • computed
    • effect(watch依赖的的底层函数)
    • track(收集依赖)
    • trigger (触发更新)

    三、 proxy回顾

    1. set和deleteProperty中需要返回布尔类型的值

    'use strict'
    // 问题1: set和deleteProperty中需要返回布尔类型的值
    // 严格模式下,如果返回false的话,会出现TypeError的异常
    const target = {
      foo: 'xxx',
      bar: 'yyy'
    }
    // Reflect.getPrototypeOf()
    // Object.getPrototypeOf()
    const proxy = new Proxy(target, {
      get (target, key, receiver) {
        // return target[key]
        return Reflect.get(target, key, receiver)
      },
      set (target, key, value, receiver) {
        // target[key] = value
        return Reflect.set(target, key, value, receiver) 
        // 这里得写return 不写默认返回undefined
        //Reflect.set执行成功或者失败会返回布尔类型的值
      },
      deleteProperty(target, key) {
        // delete target[key]
        return Reflect.deleteProperty(target, key) // 这里得写return
      }
    })
    
    proxy.foo = 'zzz'
    

    2. Proxy和Reflect中使用receiver

    • Proxy中receiver: Proxy或者继承Proxy的对象
    • Reflect中receiver:如果target对象设置了getter,getter中的this指向receiver

    执行this.bar的时候,如果第三个参数receiver不传,getter中this指向obj对象,此时proxy.fooundefined
    执行this.bar的时候,如果第三个参数receiver传, this指向代理对象,也就是获取target.bar, 此时proxy.foovalue - bar

    const obj = {
      get foo () {
        console.log(this)
        return this.bar
      }
    }
    
    const proxy = new Proxy(obj, {
      get(target, key, receiver) {
        if (key === 'bar') {
          return 'value - bar'
        }
        // 执行this.bar的时候,如果第三个参数receiver不传,getter中this指向obj对象,此时proxy.foo 为undefined
        // 执行this.bar的时候,如果第三个参数receiver传, this指向代理对象,也就是获取target.bar, 此时proxy.foo 为value - bar
        return Reflect.get(target, key, receiver)
      }
    })
    console.log(proxy.foo) // value - bar
    

    四、 源码实现

    4.1 reactive(只能转换对象)

    • 接受一个参数,判断这个参数是否是对象
    • 创建拦截器对象handler,设置get/set/deleteProperty
    • 返回Proxy对象

    代码实现:

    // 工具方法
    const isObject = val => val !==null && typeof val === 'object'
    const convert = target => isObject(target) ? reactive(target) : target
    const hasOwnproperty = Object.prototype.hasOwnProperty;
    const hasOwn = (target, key) => hasOwnproperty.call(target, key)
    
    export function reactive (target) {
        if (!isObject(target)) return target;
        const handler = {
            get (target, key, receiver) {
                //此处收集依赖 - track
                console.log('get', key)
                const result = Reflect.get(target, key, receiver);
                return convert(result)
            },
            set (target, key, value, receiver) {
                let result = true;
                const oldValue = Reflect.get(target, key, receiver)
                if (oldValue !== value) {
                    result = Reflect.set(target, key, value, receiver)
                    //此处触发更新 - trigger
                    console.log('set', key, value)
                }
                return result
            },
            deleteProperty(target, key) {
                const haskey = hasOwn(target, key)
                const result = Reflect.deleteProperty(target, key)
                if (haskey && result) {
                    //此处触发更新 - trigger
                    console.log('delete', key)
                }
                return result
            }
        }
    
        return new Proxy(target, handler);
    }
    

    实际使用:

    <body>
      <script type="module">
        import { reactive } from './reactivity/index.js'
        const obj = reactive({
          name: 'zs',
          age: 18
        })
        obj.name = 'lisi'
        delete obj.age
        console.log(obj)
      </script>
    </body>
    

    输出结果:

    set name lisi
    delete age
    Proxy {name: “lisi”}

    4.2 依赖收集

    weakMap:弱引用,当失去引用会被销毁

    vue3.0中依赖是一个三层的树形结构,我们会在最外层定义一个new weakMap()的集合targetMap,当我们触发get时使用track方法收集依赖时首先判断当前是否存在一个activeEffect对象,不存在直接返回,存在则首先判断当前targettargetMap的集合中是否存在,如果不存在就在targetMap的集合中创建对应的集合depsMap,然后判断当前keydepsMap中是否存在,如果不存在就在depsMap的集合中创建对应的集合dep,将对应的依赖activeEffect收集到对应key值对应的dep集合中。

    在这里插入图片描述

    4.3 effect、 track

    • effect:参数为一个函数(第一次会执行一次),当函数的响应式对象发生改变,就会重新执行一次函数
    • track: 收集依赖的函数
    let activeEffect = null; //当前活动的函数
    export function effect(callback) {
        activeEffect = callback; //设置当前活动对象
        callback() //此时会访问响应式对象的属性,需要收集依赖
        activeEffect = null;
    }
    let targetMap = new WeakMap() // 收集依赖的集合
    
    export function track(target, key) { //收集依赖的函数
        if (!activeEffect) return
        let depsMap = targetMap.get(target) //获取当前依赖集合中target对应的值
        if(!depsMap) { 
            targetMap.set(target, (depsMap = new Map())) //不存在则设置一个target对应的new Map()值
        }
        let dep = depsMap.get(key) //获取当前依赖集合中target对应的集合中 key属性对应的值
        if(!dep) {
            depsMap.set(key, (dep = new Set()))//不存在则在depsMap中设置一个key属性对应的new Set()值
        }
        dep.add(activeEffect) //将当前的活动对象添加到key属性对应的 Set集合中
    }
    

    4.4 trigger 触发更新

    export function trigger(target, key) {//触发更新
        const depsMap = targetMap.get(target); // 找到target对象对应的集合
        if (!depsMap) return;
        const dep = depsMap.get(key) //找到key对应的dep集合
        if(dep) {//执行每个依赖于key(响应式对象的值)值函数
            dep.forEach(effect => effect())
        }
    }
    

    4.5 ref

    可以接受对象或者基础类型,如果是响应式对象直接返回,是对象则内部会调用reactive将其转换为响应式对象,如果是普通的值则转为具有一个value属性的响应式对象

    export function ref(raw) {
      // 判断raw是否是ref创建的对象,如果是的话直接返回
      if (isObject(raw) && raw.__v_isRef)return
    
      let value = convert(raw)
      const r = {
        __v_isRef: true, //特殊标识
        get value () {
          track(r, 'value') //收集依赖
          return value
        },
        set value (newValue) {
          if(newValue !== value) {
            raw = newValue
            value = convert(raw) //将得到的新值设置为响应式对象
            trigger(r, 'value') // 触发更新
          }
        }
      }
      return r
    }
    

    以上我们可以知道 reactive vs ref

    • ref可以把基本数据类型数据转换成响应式对象

    • ref返回的对象,重新赋值成对象也是响应式的

    • reactive返回的对象,重新赋值丢失响应式

    • reactive返回的对象不可解构

    • reactive

      const product = reactive({
        name: 'iPhone',
        price: 5000,
        count: 3
      })
      
    • ref

      const price = ref(5000)
      const count = ref(3)
      

    4.6 toRefs

    传入的对象必须是reactive返回的响应式对象(proxy对象)然后将传入对象的属性转换为类似ref返回的对象然后将属性挂载在一个新的对象上返回,如果不是响应式对象(proxy对象)直接返回.

    
    export function toRefs (proxy) {
      //如果是数组我们创建一个新的数组 否则返回空对象
      const ret = proxy instanceof Array ? new Array(proxy.length) : {}
    
      for (const key in proxy) {
          // 将每一项转换为类似ref的对象
        ret[key] = toProxyRef(proxy, key)
      }
    
      return ret
    }
    
    function toProxyRef (proxy, key) {
      const r = {
        __v_isRef: true,
        get value () {
          return proxy[key]//这里不收集依赖是因为proxy是响应式对象,当我们访问响应式对象属性会触发get方法自动收集依赖
        },
        set value (newValue) {
          proxy[key] = newValue//这里不需要触发更新是因为proxy是响应式对象,当我们重新赋值会触发响应式对象的set方法触发更新
        }
      }
      return r
    }
    

    4.7 computed

    接受一个有返回值的函数作为参数,返回的值就是计算属性的值并且会监听函数中的响应式数据的变化

    
      export function computed (getter) {
      const result = ref()
    
      effect(() => (result.value = getter()))
    
     	 	return result
      }
    

    五、完整示例代码

    https://gitee.com/liannian9/fed-e-task-03-05/tree/master/code/%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86/01-reactivity

    展开全文
  • 主要介绍了Vue3.0 响应式系统源码逐行分析讲解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 这篇文章主要讲讲 Vue 的响应式系统,形式与前边的稍显 不同吧,分析为主,源码为辅,如果能达到深入浅出的效果那就更好了。 什么是响应式系统响应式系统」一直以来都是我认为 Vue 里最核心的几个概念之一。想...

    前言

    前面几篇文章一直都以源码分析为主,其实枯燥无味,对于新手玩家来说很不友好。这篇文章主要讲讲 Vue 的响应式系统,形式与前边的稍显 不同吧,分析为主,源码为辅,如果能达到深入浅出的效果那就更好了。

    什么是响应式系统

    「响应式系统」一直以来都是我认为 Vue 里最核心的几个概念之一。想深入理解 Vue ,首先要掌握「响应式系统」的原理。

    从一个官方的例子开始

    由于 Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值:

    
    var vm = new Vue({
      data: {
        // 声明 message 为一个空值字符串
        message: ''
      },
      template: '<div>{{ message }}</div>'
    })
    // 之后设置 `message`
    vm.message = 'Hello!'
    复制代码

    如果你未在 data 选项中声明 message,Vue 将警告你渲染函数正在试图访问不存在的属性。

    当然,仅仅从上面这个例子我们也只能知道,Vue不允许动态添加根级响应式属性。这意味我们需要将使用到的变量先在data函数中声明。

    抛砖?引玉

    新建一个空白工程,加入以下代码

    export default {
        name: 'JustForTest',
        data () {
            return {}
        },
        created () {
            this.b = 555
            console.log(this.observeB)
            this.b = 666
            console.log(this.observeB)
        },
        computed: {
        	observeB () {
                return this.b
        	}
        }
    }
    复制代码

    运行上述代码,结果如下:

    555
    555
    复制代码

    在上面的代码中我们做了些什么?

    1. 没有在 data 函数中声明变量(意味着此时没有根级响应式属性)
    2. 定义了一个 computed 属性 —— observeB ,用来返回(监听)变量b
    3. 使用了变量 b 同时赋值 555 ,打印 this.observeB
    4. 使用了变量 b 同时赋值 666 ,打印 this.observeB

    打印结果为什么都是555

    有段简单的代码可以解释这个原因:

    function createComputedGetter (key) {
      return function computedGetter () {
        var watcher = this._computedWatchers && this._computedWatchers[key];
        if (watcher) {
          if (watcher.dirty) {
            watcher.evaluate();
          }
          if (Dep.target) {
            watcher.depend();
          }
          return watcher.value
        }
      }
    }
    ...
    Watcher.prototype.evaluate = function evaluate () {
      this.value = this.get();
      this.dirty = false;
    };
    复制代码

    createComputedGetter函数返回一个闭包函数并挂载在computed属性的getter上,一旦触发computed属性的getter, 那么就会调用computedGetter

    显然,输出 555 是因为触发了 this.observeBgetter ,从而触发了 computedGetter ,最后执行 Watcher.evalute()
    然而,决定 watcher.evalute() 函数执行与否与 watcherwatcher.dirty 的值是否为空有关

    深入了解响应式系统

    Object.defineProperty

    Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

    那么这个函数应该怎么使用呢?给个官方的源码当做例子:

    function def (obj, key, val, enumerable) {
      Object.defineProperty(obj, key, {
        value: val,
        enumerable: !!enumerable,
        writable: true,
        configurable: true
      });
    }
    def(value, '__ob__', this);
    复制代码

    gettersetter

    上面提到了 Object.defineProperty 函数,其实这个函数有个特别的参数 —— descriptor(属性描述符),简单看下MDN 上的定义:

    对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是 可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。

    其中需要特别提到的就是 gettersetter,在 descriptor(属性描述符)中分别代表 get 方法和 set 方法

    get

    一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入, 但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。

    set

    一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数, 即该属性新的参数值。

    小结

    1. 对象在被访问时会触发getter
    2. 对象在被赋值是会触发setter
    3. 利用getter我们可以知道哪些对象被使用了
    4. 利用setter我们可以知道哪些对象被赋值了

    依赖收集

    Vue基于Object.defineProperty函数,可以对变量进行依赖收集,从而在变量的值改变时触发视图的更新。简单点来讲就是: Vue需要知道用到了哪些变量,不用的变量就不管,在它(变量)变化时,Vue就通知对应绑定的视图进行更新。 举个例子:

     Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
          var value = getter ? getter.call(obj) : val;
          if (Dep.target) {
            dep.depend();
            if (childOb) {
              childOb.dep.depend();
              if (Array.isArray(value)) {
                dependArray(value);
              }
            }
          }
          return value
        },
        set: function reactiveSetter (newVal) {
          var value = 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();
          }
          // #7981: for accessor properties without setter
          if (getter && !setter) { return }
          if (setter) {
            setter.call(obj, newVal);
          } else {
            val = newVal;
          }
          childOb = !shallow && observe(newVal);
          dep.notify();
        }
      });
    复制代码

    这段代码做了哪些事情呢?主要有以下几点:

    • 对于 obj[key],定义它的 getset 函数
    • obj[key] 被访问时,触发 get 函数,调用 dep.depend 函数收集依赖
    • obj[key] 被赋值时,调用 set 函数,调用 dep.notify 函数触发视图更新

    如果你再深入探究下去,那么还会发现 dep.notify 函数里还调用了 update 函数,而它恰好就是 Watcher 类所属 的方法,上面所提到的 computed 属性的计算方法也恰好也属于 Watcher

    Observer

    前面所提到的 Object.defineProperty 函数到底是在哪里被调用的呢?答案就是 initData 函数和 Observer类。 可以归纳出一个清晰的调用逻辑:

    • 初始化 data 函数,此时调用 initData 函数
    • 在调用 initData 函数时,执行 observe 函数,这个函数执行成功后会返回一个 ob 对象
    • observe 函数返回的 ob 对象依赖于 Observer 函数
    • Observer 分别对对象和数组做了处理,对于某一个属性,最后都要执行 walk 函数
    • walk 函数遍历传入的对象的 key 值,对于每个 key 值对应的属性,依次调用 defineReactive$$1 函数
    • defineReactive$$1 函数中执行 Object.defineProperty 函数
    • ...

    感兴趣的可以看下主要的代码,其实逻辑跟上面描述的一样,只不过步骤比较繁琐,耐心阅读源码的话还是能看懂。

    initData

    function initData (vm) {
      var data = vm.$options.data;
      data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {};
      if (!isPlainObject(data)) {
        data = {};
        ...
      }
      // proxy data on instance
      var keys = Object.keys(data);
      var props = vm.$options.props;
      var methods = vm.$options.methods;
      var i = keys.length;
      while (i--) {
        var key = keys[i];
        ...
        if (props && hasOwn(props, key)) {
            ...
        } else if (!isReserved(key)) {
          proxy(vm, "_data", key);
        }
      }
      // observe data
      observe(data, true /* asRootData */);
    }
    复制代码

    observe

    function observe (value, asRootData) {
      if (!isObject(value) || value instanceof VNode) {
        return
      }
      var ob;
      if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        ob = value.__ob__;
      } else if (
        shouldObserve &&
        !isServerRendering() &&
        (Array.isArray(value) || isPlainObject(value)) &&
        Object.isExtensible(value) &&
        !value._isVue
      ) {
        ob = new Observer(value);
      }
      if (asRootData && ob) {
        ob.vmCount++;
      }
      return ob
    }
    复制代码

    Observer

    var Observer = function Observer (value) {
      this.value = value;
      this.dep = new Dep();
      this.vmCount = 0;
      def(value, '__ob__', this);
      if (Array.isArray(value)) {
        if (hasProto) {
          protoAugment(value, arrayMethods);
        } else {
          copyAugment(value, arrayMethods, arrayKeys);
        }
        this.observeArray(value);
      } else {
        this.walk(value);
      }
    };
    复制代码

    更加方便的定义响应式属性

    文档中提到,Vue 建议在根级声明变量。通过上面的分析我们也知道,在 data 函数中 声明变量则使得变量变成「响应式」的,那么是不是所有的情况下,变量都只能在 data 函数中 事先声明呢?

    $set

    Vue 其实提供了一个 $set 的全局函数,通过 $set 就可以动态添加响应式属性了。

    export default {
    	data () {
            return {}
        },
        created () {
            this.$set(this, 'b', 666)
        },
    }
    复制代码

    然而,执行上面这段代码后控制台却报错了
    [Vue warn]: Avoid adding reactive properties to a Vue instance or its root $data at runtime - declare it upfront in the data option.

    其实,对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。 $set 函数的执行逻辑:

    • 判断实例是否是数组,如果是则将属性插入
    • 判断属性是否已定义,是则赋值后返回
    • 判断实例是否是 Vue 的实例或者是已经存在 ob 属性(其实也是判断了添加的属性是否属于根级别的属性),是则结束函数并返回
    • 执行 defineReactive$$1,使得属性成为响应式属性
    • 执行 ob.dep.notify(),通知视图更新

    相关代码:

    function set (target, key, val) {
      if (process.env.NODE_ENV !== 'production' &&
        (isUndef(target) || isPrimitive(target))
      ) {
        warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
      }
      if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.length = Math.max(target.length, key);
        target.splice(key, 1, val);
        return val
      }
      if (key in target && !(key in Object.prototype)) {
        target[key] = val;
        return val
      }
      var ob = (target).__ob__;
      if (target._isVue || (ob && ob.vmCount)) {
        process.env.NODE_ENV !== 'production' && warn(
          'Avoid adding reactive properties to a Vue instance or its root $data ' +
          'at runtime - declare it upfront in the data option.'
        );
        return val
      }
      if (!ob) {
        target[key] = val;
        return val
      }
      c(ob.value, key, val);
      ob.dep.notify();
      return val
    }
    复制代码

    数组操作

    为了变量的响应式,Vue 重写了数组的操作。其中,重写的方法就有这些:

    • push
    • pop
    • shift
    • unshift
    • splice
    • sort
    • reverse

    那么这些方法是怎么重写的呢?
    首先,定义一个 arrayMethods 继承 Array

    var arrayProto = Array.prototype;
    var arrayMethods = Object.create(arrayProto);
    复制代码

    然后,利用 object.defineProperty,将 mutator 函数绑定在数组操作上:

    def(arrayMethods, method, function mutator () { ... })
    复制代码

    最后在调用数组方法的时候,会直接执行 mutator函数。源码中,对这三种方法做了特别 处理:

    • push
    • unshift
    • splice

    因为这三种方法都会增加原数组的长度。当然如果调用了这三种方法,会再调用一次 observeArray 方法(这里的逻辑就跟前面提到的一样了)
    最后的最后,调用 notify 函数

    核心代码:

    methodsToPatch.forEach(function (method) {
     // cache original method
     var original = arrayProto[method];
     def(arrayMethods, method, function mutator () {
       var args = [], len = arguments.length;
       while ( len-- ) args[ len ] = arguments[ len ];
    
       var result = original.apply(this, args);
       var ob = this.__ob__;
       var inserted;
       switch (method) {
         case 'push':
         case 'unshift':
           inserted = args;
           break
         case 'splice':
           inserted = args.slice(2);
       }
       if (inserted) { ob.observeArray(inserted); }
       // notify change
       ob.dep.notify();
       return result
     });
    });
    复制代码

    总结

    「响应式原理」借助了这三个类来实现,分别是:

    • Watcher
    • Observer
    • Dep

    初始化阶段,利用 getter 的特点,监听到变量被访问 ObserverDep 实现对变量的「依赖收集」, 赋值阶段利用 setter 的特点,监听到变量赋值,利用 Dep 通知 Watcher,从而进行视图更新。

    参考资料

    深入响应式原理

    扫描下方的二维码或搜索「tony老师的前端补习班」关注我的微信公众号,那么就可以第一时间收到我的最新文章。

    转载于:https://juejin.im/post/5cc27c65f265da037a3cf14b

    展开全文
  • 什么是响应式系统

    千次阅读 2018-08-07 20:04:51
    什么是响应式系统(Reactive System),为什么需要响应式系统? 几年之前,大型应用就是几台服务器,几个G的数据,数秒的响应时间,甚至还可以有每天几个小时的离线维护时间。但是现在,随着设备终端的极具增多,...
  • 响应式系统 UI 在 MVVM 中指的是 View,状态在 MVVM 中指的是 Modal,而保证 View 和 Modal 同步的是 View-Modal。 Vue 通过一个响应式系统保证了View 与 Modal的同步,由于要兼容IE,Vue 选择了Object....
  • Vue的响应式系统 Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的JavaScript 对象,而当你修改它们时,视图会进行更新,这使得状态管理非常简单直接,我们可以只关注数据本身,而不用手动...
  • 编写一个mini观察器来解析Vue2.0的响应式系统。 可能大部分人都已知道了Vue2.0是采用Object.defineProperty()这个API进行实现,现在我们从0开始通过Object.defineProperty()编写一个mini观察器理解Vue响应式的原理...
  • 主要介绍了这应该是最详细的响应式系统讲解了,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 什么是响应式系统? Vue的响应式系统是一个精心搭建的监控系统,它负责监测项目中的数据变化,然后通知对该数据“感兴趣”的订阅者进行相关操作。我们分别来理解“数据”、“感兴趣”以及“订阅者”这三个关键词。 ...
  • Vue3.0 对响应式系统模块进行重构,不再用Object.defineProperty了,而是改用Proxy。这样做大体上有两个好处,一是不用对数组进行单独处理了,去掉了Vue.set 和 Vue.delete接口,因为Proxy可以对整个对象进行拦截...
  • vue-响应式系统

    千次阅读 2018-02-12 16:24:25
    vue作为一个前端框架,近两年非常的火,虽然它的社区不像react那样繁荣,但它配套的东西都有固定的 ...是它的响应式系统。 用一张图来表示的话就是这样 归纳起来,Vue.js在这里主要做了三件事: ...
  • 后台框架神器,响应式框架,美观,好用,后台开发模板
  • 理清Vue响应式系统中的Watcher 一、什么是响应系统中的Watcher,它的作用是什么? 响应系统中的Watcher即这个系统的观察者,它是响应系统中观察者模式的载体,当响应系统中的数据发生改变的时候,它能够知道并且执行...
  • 这篇文章主要是结合源码分析vue响应式系统的原理和实现。 代理 下面这段代码是vue使用的典型方式: &lt;div id="app-5"&gt; &lt;p&gt;{{ message }}&lt;/p&gt; &lt;button ...
  • bootstrap的一个系统界面,支持相应!有助于分享插件的高亮组合
  • Vue中data的响应式系统

    2021-03-31 18:35:46
    在Vue中声明组件时,如果...Vue会递归地将data选择中的数据加入响应式系统,但这些数据应该是声明时即存在的,下面来看一段提示代码。 <div id="app"> <p>{{title}}</p> <p>{{profile}}&l
  • Vue 最巧妙的特性之一是其响应式系统,而我们也能够在仓库的 packages/reactivity 模块下找到对应的实现。虽然源码的代码量不多,网上的分析文章也有一堆,但是要想清晰地理解响应式原理的具体实现过程,还是挺费...
  • Vue.js 是一款 MVVM 框架,数据模型仅仅是普通的 JavaScript 对象,但是对这些对象进行操作时,却能影响对应视图,它的核心实现就是「响应式系统」。尽管我们在使用 Vue.js 进行开发时不会直接修改「响应式系统」,...
  • 基于响应式后台!通俗易懂!直接进项点击响应!当前点击停止默认行为!点击地址则是对应的数字iframe
  • 一、响应式系统原理 - 介绍 Proxy对象实现属性监听 默认监听动态添加的属性 默认监听属性的删除操作 默认监听数组索引和 length属性 可以作为单独的模块使用 多层属性嵌套,在访问属性过程中处理下一级属性 核心...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,143
精华内容 4,057
关键字:

响应式系统