Vue双向绑定原理

实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。

watcher:订阅者 watcher类用于定义一个订阅者,构造函数传入一个参数为vue对象,一个为data的属性(是node节点的v-model或v-on:click等指令的属性值。如v-model=“name”,exp就是name;),一个回调函数cb。这个回调函数cb就是发布者在notify时,订阅者会执行的回调函数。





初始化时要将自己添加到订阅器Dep里,核心:添加Dep.target为自己(this),判断if(Dep.target)是否要添加订阅者.dep.addsubs(Dep.target)之后让Dep.target赋值为null,添加成功后再将其去掉

1 let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
2 在这个过程中会对 vm 上的数据访问,其实就是为了触发数据对象的getter。
3 4 每个对象值的 getter都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 方法,
5 也就会执行this.addSub(Dep.target),即把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,
6 这个目的是为后续数据变化时候能通知到哪些 subs 做准备。
7 ————————————————
8 版权声明:本文为CSDN博主「狗焕sama」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
9 原文链接:https://blog.csdn.net/qq_42376204/article/details/108423333

Dep:既是订阅器核心,也是发布者:subs[]收集订阅者,notify通知订阅者数据更新了,其中subs和notify都是订阅者watcher里的属性值


observer:监听器 观察者(关联发布者和订阅者)核心:用Object.defineProperty监听所有属性,发生变化告诉watcher

解析器:Compile 解析dom节点。获取到dom元素,然后对含有dom元素上含有指令的节点进行处理,因此这个环节需要对dom操作比较频繁,所有可以先建一个fragment片段,将需要解析的dom节点存入fragment片段里再进行处理:
获取到最外层节点后,调用compileElement函数,对所有子节点进行判断,如果节点是文本节点且匹配{{}}这种形式指令的节点就开始进行编译处理,编译处理首先需要初始化视图数据,

  1. Vue双向绑定原理的时候,发现订阅器Dep是可以直接调用他自己的target的属性,但是class本质是一个函数,为此确认了一下
     1 //为啥Dep类本质是个函数,可以直接调用属性?
     2     class HanClass{
     3       constructor(){
     4         this.name = 'xixixix'
     5       }
     6     }
     7     function HanFn(){
     8       this.name = 'gogogo'
     9     }
    10     console.log(HanFn.name);//函数也是可以直接调用自己的属性的,无需实例化!
    11     console.log(HanClass.name);

    结论:函数是可以直接调用到自己本身的方法的

  2. 如何解释Vue的双向绑定原理?
    双向绑定原理是利用发布订阅模式结合数据劫持来实现的,发布订阅模式是指我们有一个监听器observer,一个订阅器Dep,
    一个订阅者watcher。
    我们把data里的属性通过监听器observer全部绑定了数据劫持,数据劫持的get方法中判断是否要添加watcher到订阅器数组subs里面,也就是在我们初始化的data的时候,这个data的属性其实就是一个订阅者watcher,
    set方法执行了订阅器的notify方法通知watcher,
    他在初始化的时候construtor传入的vm为Vue实例,exp为这个data的属性,最后一句this.value = this.get()强制执行了get方法,
    而此时get方法里Dep.target = this让订阅器的target为自己,然后将为自己添加到订阅器Dep的订阅者数组subs里,执行完之后让
    Dep.target = null即可
    

      

    HTML
     1 <!DOCTYPE html>
     2 <html lang="en">
     3 
     4 <head>
     5   <meta charset="UTF-8">
     6   <title>Document</title>
     7 </head>
     8 
     9 <body>
    10   <h1 id="name"></h1>
    11   <input type="text">
    12   <input type="button" value="改变data内容" onclick="changeInput()">
    13 
    14   <script src="./observer.js"></script>
    15   <script src="./watcher.js"></script>
    16   <script>
    17     function myVue(data, el, exp) {
    18       console.log('进入了myVue......');
    19       this.data = data;
    20       observable(data); //将数据变的可观测
    21       el.innerHTML = this.data[exp]; // 初始化模板数据的值
    22       new Watcher(this, exp, function (value) {//在这个函数里只执行一次,但是此时已经添加到subs数组里了,里面的这个回调函数非常关键!!!因为是引用类型,所以notify每次去updata的时候,其实也执行了这段代码,所以起到了修改innerHTML的作用!!!
    23         el.innerHTML = value;
    24         console.log('进入了Watcher类.....');
    25       });
    26       console.log('进入了myVue函数.....');
    27       return this;
    28     }
    29 
    30     var ele = document.querySelector('#name');
    31     var input = document.querySelector('input');
    32 
    33     var myVue = new myVue({
    34       name: 'hello world'
    35     }, ele, 'name');
    36 
    37     //改变输入框内容
    38     input.oninput = function (e) {
    39       myVue.data.name = e.target.value
    40     }
    41     //改变data内容
    42     function changeInput() {
    43       myVue.data.name = "难凉热血"
    44     }
    45   </script>
    46 </body>
    47 
    48 </html>

    observer.js:

     1 //observer
     2   /**
     3      * 把一个对象的每一项都转化成可观测对象
     4      * @param { Object } obj 对象
     5      */
     6     function observable (obj) {
     7         if (!obj || typeof obj !== 'object') {
     8             return;
     9         }
    10         let keys = Object.keys(obj);
    11         keys.forEach((key) =>{
    12             defineReactive(obj,key,obj[key])
    13         })
    14         return obj;
    15     }
    16     /**
    17      * 使一个对象转化成可观测对象
    18      * @param { Object } obj 对象
    19      * @param { String } key 对象的key
    20      * @param { Any } val 对象的某个key的值
    21      */
    22     function defineReactive (obj,key,val) {
    23     let dep = new Dep();
    24         Object.defineProperty(obj, key, {
    25             get(){
    26         console.log('Dep.target。。。。。。', Dep.target);
    27                 dep.depend();
    28                 console.log(`${key}属性被读取了`);
    29                 return val;
    30             },
    31             set(newVal){
    32                 val = newVal;
    33                 console.log(`${key}属性被修改了`);
    34                 dep.notify()                    //数据变化通知所有订阅者
    35             }
    36         })
    37     }
    38     class Dep {
    39         
    40         constructor(){
    41             this.subs = []
    42         }
    43         //增加订阅者
    44         addSub(sub){
    45             this.subs.push(sub);
    46         }
    47         //判断是否增加订阅者
    48         depend () {
    49             if (Dep.target) {
    50                  this.addSub(Dep.target)
    51             }
    52         }
    53 
    54         //通知订阅者更新
    55         notify(){
    56             this.subs.forEach((sub) =>{
    57         console.log('此时的sub....',sub);
    58                 sub.update()
    59             })
    60         }
    61         
    62     }
    63     // Dep.target = null;

    watcher.js

     1 //watcher
     2 class Watcher {
     3   constructor(vm, exp, cb) {
     4     this.vm = vm;
     5     this.exp = exp;
     6     this.cb = cb;
     7     this.value = this.get(); // 将自己添加到订阅器的操作
     8   }
     9   get() {
    10     Dep.target = this; // 缓存自己
    11     let value = this.vm.data[this.exp] // 强制执行监听器里的get函数
    12     Dep.target = null; // 释放自己
    13     return value;
    14   }
    15   update() {
    16     let value = this.vm.data[this.exp];
    17     let oldVal = this.value;
    18     if (value !== oldVal) {
    19       console.log('sub数组的订阅者Watcher实例的updata方法......');
    20       this.value = value;
    21       this.cb.call(this.vm, value, oldVal);
    22     }
    23   }
    24 }

    参考网址:

    1. https://www.cnblogs.com/canfoo/p/6891868.html
    2. https://www.cnblogs.com/wangjiachen666/p/9883916.html
posted @ 2022-05-11 19:25  Adinsclay  阅读(82)  评论(0)    收藏  举报