zoukankan      html  css  js  c++  java
  • vue数据双向绑定原理

    什么是数据响应式?

    数据响应式即数据双向绑定,就是把Model绑定到view,当我们通过js修改Model,View会自动更新;若我们更新了View,Model的数据也会自动更新,这就是双向绑定。 

    数据响应式原理

    vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的,

    那么vue是如果进行数据劫持的???

    1)vue2.0版本是利用了Object.defineProperty()这个方法重新定义对象获取属性值的get和设置属性值set的操作来实现的。

    2)vue3.0版本采用了Es6的Proxy对象来实现。

    我们先分别了解下defineProperty方法、Proxy对象。

    什么是defineProperty?

    defineProperty简言之,是定义对象的属性。它可以来控制一个对象属性的一些特有操作,比如读写权、是否可以枚举等。

    它其实并不是核心的为一个对象做数据绑定,而是给对象做属性标签。定义对象的属性。只不过是属性的get和set实现了响应式。

    属性名 默认值
    value undefined
    get undefined
    set undefined
    writable false
    enumerable false
    configurable false

      

    接下来,我们先研究下它对应的两个描述属性get和set。

    在平常,我们很容易就可以打印出一个对象的属性数据:

    var person = {
      name: '小白'
    };
    console.log(person.name);  // 小白

    如果想要在执行console.log(person.name)的同时,直接给书名加个书名号,那要怎么处理呢?或者说要通过什么监听对象 person 的属性值。

    这时候Object.defineProperty( )就派上用场了,代码如下:

    var person = {
      name: '小白'
    }
    var value = person.name
    Object.defineProperty(person, 'name', {
      set: function (newValue) {
        value = newValue
        return '名字是' +  value;
      },
      get: function () {
        return '***' + value + '***'
      }
    })

    结果展示:

     

    我们通过Object.defineProperty( )设置了对象person的name属性,对其get和set进行重写操作。

    顾名思义,get就是在读取name属性这个值触发的函数,set就是在设置name属性这个值触发的函数。

    实现一个简单完整版的mvvm双向绑定代码。 

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
        <title>vuetest</title>
      </head>
      <body>
        <div id="app">
          <input v-model="text" id="test"></input>
          <div id="show"></div>
        </div>
        <!-- built files will be auto injected -->
      </body>
    
      <script>
    var obj = {
        a:1,
        b:2
    };
    var _value = obj.a
    Object.defineProperty(obj,'a',{
        get:function(){
            console.log("get方法")
            return _value
        },
        set:function(newValue){
            console.log("set方法")
            _value = newValue
            document.getElementById('test').value   =_value
            document.getElementById('show').innerHTML  =_value
            return _value
        }
    });
    document.getElementById('test').addEventListener('input',function(e){
        obj.a = e.target.value;
    })
    </script>
    </html>

    Object.defineProperty的缺点:

    无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。

    https://www.cnblogs.com/YikaJ/p/4278255.html

    所以vue才设置了7个变异数组(push、pop、shift、unshift、splice、sort、reverse)的 hack 方法来解决问题。

    只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。如果能直接劫持一个对象,就不需要递归 + 遍历了

    监听数组的改变

    1)先拷贝一份数组原型链 

    2)定义一个方法总类

    3)遍历数组,给数组原型链重新写方法,然后触发更新 dep.notify

     

    proxy代理(详情点击 )

     

    Proxy对象用于定义基本操作的自定义行为,和defineProperty功能类似,只不过用法有些不同

    上述例子,用Proxy来替换defineProperty进行数据劫持。

    var proxyObj = new Proxy(obj, {
      get:function(target, key, receiver){ //我们在这里拦截到了数据
            console.log("get方法",target,key,receiver)
            return true
        },
        set:function(target,key,value, receiver){  //改变数据的值,拦截下来额
            console.log("set方法",target,key,value, receiver)
            target[key]= value
            document.getElementById('test').value= value
            document.getElementById('show').innerHTML=value
            return true
        }
    })
    
    document.getElementById('test').addEventListener('input',function(e){
      proxyObj.a = e.target.value;
    })

    为什么vue3中改用proxy

    1)defineProperty只能监听某个属性,不能对全对象监听,所以可以省去for in 提升效率

    2)可以监听数组,不用再去单独对数组做操作

    3)Proxy只是代理了原对象,不会污染原对象

    那么,在vue中从一个数据到发生改变的过程是什么?

    发布者-订阅者模式

    Observer是个监听器,用来监听vue中的data中定义的属性。

    通过Obeject.defineProperty()来监听数据的变动,通过递归方法遍历所有属性值。如果属性发上变化了,会通知给对应的dep。

    Dep 在监听器Observer和订阅者Watcher之间进行统一管理的。

    主要的作用就是收集观察者Watcher和通知观察者目标更新。每个属性拥有自己的消息订阅器dep,Dep实例里面存放所有订阅了该属性的观察者对象,

    当数据发生改变时,会遍历订阅者列表(dep.subs),通过dep.notify()通知Watcher。


    depend 实例方法用来收集依赖,notify 实例方法用来触发依赖的执行。经过了 depend => watcher.addDep => addSub (watcher 表示 Watcher 的一个实例)之后,subs 中收集的依赖实际上都是 Watcher 实例,再经过 notify => watcher.update 之后就可以触发实例化 Watcher 时的渲染函数和回调函数(如果有)的执行了。

    Watcher类主要用来收集依赖和触发更新。 主要作用是为观察属性提供回调函数以及收集依赖(如计算属性computed,vue会把该属性所依赖数据的dep添加到自身的deps中),

    Compile是指令解析器,解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数

     

     

    }
     

    被监听的数据进行取值操作时(getter),如果存在Dep.target(某一个观察者),则说明这个观察者是依赖该数据的(如计算属性中,计算某一属性会用到其他已经被监听的数据,就说该属性依赖于其他属性,会对其他属性进行取值),就会把这个观察者添加到该数据的订阅器subs里面,留待后面数据变更时通知(会先通过观察者id判断订阅器中是否已经存在该观察者),同时该观察者也会把该数据的订阅器dep添加到自身deps中,方便其他地方使用。

    被监听的数据进行赋值操作时(setter)时,就会触发dep.notify(),循环该数据订阅器中的观察者,进行更新操作。

    为什么要进行依赖收集?

    new Vue({

        data(){

            return {

                 name:'zane',

                 sex:'男'

            }

        }

    })

    假设页面只使用到了name,并没有使用sex,根据Object.defineProperty的转换,如果我们设置了this.sex='女',那么Vue也会去执行一遍虚拟DOM的比较,

    这样就无形的浪费了一些性能,因此才需要做依赖收集,页面用到了就收集,没有用到就不收集。

     

    首先根据上图实现整体的一个架构,用到订阅发布者的设计模式。

    然后实现MVVM中的由M到V,把模型里面的数据绑定到视图。

    最后实现V-M,当文本框输入文本,触发更改模型中的数据,及更新相对应的视图。

    我们可以先来看一下通过控制台输出一个定义在vue初始化数据上的对象是个什么东西。

      <div id="app">
          <input v-model="text" id="test"></input>
          <div id="show"></div>
       </div>
    class Vue{
      constructor(options){
        console.log(options)
      }
    }
    const app = new Vue({
        el: '#app',
        data:{
          text: 'test'
        }
    })

    打印:

     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);
        }
      };
     /**
       * Walk through all properties and convert them into
       * getter/setters. This method should only be called when
       * value type is Object.
       * 即浏览所有属性并将其转换为get/set。仅当值类型为“对象”时才应调用此方法。
       */
      Observer.prototype.walk = function walk (obj) {
        var keys = Object.keys(obj);
        for (var i = 0; i < keys.length; i++) {
          defineReactive$$1(obj, keys[i]);
        }
      };
     /**
       * Observe a list of Array items.
       * 即观察数组项列表
       */
      Observer.prototype.observeArray = function observeArray (items) {
        for (var i = 0, l = items.length; i < l; i++) {
          observe(items[i]);
        }
      };
  • 相关阅读:
    素数筛的2种方法
    c++含结构体的sort()使用
    构建c++二维vector
    c语言输入单字符避免回车的四种方法
    menset()在c++中的作用
    杭电oj hud1092 1093 活用EOF&n--
    EOF在while(scanf("%d",&n))中的作用
    KMP算法
    图解HTTP(3)
    图解HTTP(2)
  • 原文地址:https://www.cnblogs.com/renzm0318/p/12289415.html
Copyright © 2011-2022 走看看