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,添加成功后再将其去掉

let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
在这个过程中会对 vm 上的数据访问,其实就是为了触发数据对象的getter。
​
每个对象值的 getter都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 方法,
也就会执行this.addSub(Dep.target),即把当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,
这个目的是为后续数据变化时候能通知到哪些 subs 做准备。











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





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






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

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

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

  1. 如何解释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:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>

<body>
  <h1 id="name"></h1>
  <input type="text">
  <input type="button" value="改变data内容" onclick="changeInput()">

  <script src="./observer.js"></script>
  <script src="./watcher.js"></script>
  <script>
    function myVue(data, el, exp) {
      console.log('进入了myVue......');
      this.data = data;
      observable(data); //将数据变的可观测
      el.innerHTML = this.data[exp]; // 初始化模板数据的值
      new Watcher(this, exp, function (value) {//在这个函数里只执行一次,但是此时已经添加到subs数组里了,里面的这个回调函数非常关键!!!因为是引用类型,所以notify每次去updata的时候,其实也执行了这段代码,所以起到了修改innerHTML的作用!!!
        el.innerHTML = value;
        console.log('进入了Watcher类.....');
      });
      console.log('进入了myVue函数.....');
      return this;
    }

    var ele = document.querySelector('#name');
    var input = document.querySelector('input');

    var myVue = new myVue({
      name: 'hello world'
    }, ele, 'name');

    //改变输入框内容
    input.oninput = function (e) {
      myVue.data.name = e.target.value
    }
    //改变data内容
    function changeInput() {
      myVue.data.name = "难凉热血"
    }
  </script>
</body>

</html>

observer.js:

//observer
  /**
	 * 把一个对象的每一项都转化成可观测对象
	 * @param { Object } obj 对象
	 */
	function observable (obj) {
		if (!obj || typeof obj !== 'object') {
        	return;
    	}
		let keys = Object.keys(obj);
		keys.forEach((key) =>{
			defineReactive(obj,key,obj[key])
		})
		return obj;
	}
	/**
	 * 使一个对象转化成可观测对象
	 * @param { Object } obj 对象
	 * @param { String } key 对象的key
	 * @param { Any } val 对象的某个key的值
	 */
	function defineReactive (obj,key,val) {
    let dep = new Dep();
		Object.defineProperty(obj, key, {
			get(){
        console.log('Dep.target。。。。。。', Dep.target);
				dep.depend();
				console.log(`${key}属性被读取了`);
				return val;
			},
			set(newVal){
				val = newVal;
				console.log(`${key}属性被修改了`);
				dep.notify()                    //数据变化通知所有订阅者
			}
		})
	}
	class Dep {
		
		constructor(){
			this.subs = []
		}
		//增加订阅者
		addSub(sub){
			this.subs.push(sub);
		}
        //判断是否增加订阅者
		depend () {
		    if (Dep.target) {
		     	this.addSub(Dep.target)
		    }
		}

		//通知订阅者更新
		notify(){
			this.subs.forEach((sub) =>{
        console.log('此时的sub....',sub);
				sub.update()
			})
		}
		
	}
	// Dep.target = null;

watcher.js

//watcher
class Watcher {
  constructor(vm, exp, cb) {
    this.vm = vm;
    this.exp = exp;
    this.cb = cb;
    this.value = this.get(); // 将自己添加到订阅器的操作
  }
  get() {
    Dep.target = this; // 缓存自己
    let value = this.vm.data[this.exp] // 强制执行监听器里的get函数
    Dep.target = null; // 释放自己
    return value;
  }
  update() {
    let value = this.vm.data[this.exp];
    let oldVal = this.value;
    if (value !== oldVal) {
      console.log('sub数组的订阅者Watcher实例的updata方法......');
      this.value = value;
      this.cb.call(this.vm, value, oldVal);
    }
  }
}

参考网址

  1. https://www.cnblogs.com/canfoo/p/6891868.html
  2. https://www.cnblogs.com/wangjiachen666/p/9883916.html(强烈推荐,写的目前看过最好的)
  • 3
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值