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函数,对所有子节点进行判断,如果节点是文本节点且匹配{{}}这种形式指令的节点就开始进行编译处理,编译处理首先需要初始化视图数据,
- 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);
结论:函数是可以直接调用到自己本身的方法的
- 如何解释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 }
参考网址:
- https://www.cnblogs.com/canfoo/p/6891868.html
- https://www.cnblogs.com/wangjiachen666/p/9883916.html
浙公网安备 33010602011771号