Vue双向数据绑定的那些事,真的知道吗?

Vue双向数据绑定,Model是如何改变View,View又是如何改变Model的

1. 原理解析

大家可能发现IE8以下的浏览器不兼容Vue.js,只是因为Vue的双向数据绑定核心是利用ES5的Object.defineProperty。

1. Object.defineProperty

对于Object.defineProperty来说,它会直接在对象上定义一个新的属性,或者修改一个对象的现有属性,它的返回值就是这个对象。
Object.defineProperty(obj, prop, descriptor)属性解释

  • obj 定义属性的对象
  • prop 要定义或修改的属性的名称
  • descriptor将被定义或修改属性的描述符(这是核心)
2. observe

对于observe来说,它的功能就是用来检测数据的变化。实现方式是给非VNode的对象类型数据添加一个Observer,如果已经添加了就直接返回,否则去实例化一个Observer对象实例,但是需要满足一定的条件.
对于Observer来说,它是一个类,通过给对象添加getter和setter来实现依赖收集和派发更新.

  • 依赖收集(getter)
    const dep= new Dep() //实例化一个Dep实例
    
    在get函数中通过dep.depend做依赖收集

下面说说Dep:
  Dep是一个Class,定义了一些属性和方法,它有一个静态属性target,这是一个全局唯一Watcher。对于Watcher来说,同一时间内只有一个全局的Watcher被计算。这么看来Dep就是对Watcher的一种管理,Dep如果脱离了Watcher是没有意义的。而Watcher和Dep的配合就是一个典型的观察者设计模式。
下面说说Watcher:
  Watcher是一个Class,在它的构造函数中定义了一些和Dep相关的属性:

this.dep=[]
this.newDeps=[]
this.depIds = new Set()
this.newDepIds = new Set()
  • 收集过程:
      在实例化一个渲染watcher的时候,首先进入watcher的构造函数逻辑,然后执行this.get()方法,进入get函数把Dep.target赋值为当前渲染的watcher并且将它压栈。然后使用vm._render()方法,生成渲染VNode,并且在在这个过程中对vm上的数据访问,这个时候就触发数据对象的getter,在这个过程内执行Dep.target.addDep(this)方法,将watcher订阅到这个数据持有的deo的subs中,为后续数据变化时通知到哪些subs做准备。最后递归遍历添加所有的子项的getter。
      Watcher在构造函数中初始化两个Dep实例数组。newDeps代表添加的Dep实例数组,deps代表上一次添加的Dep实例数组。
      依赖清空:在执行清空依赖(cleanupDeps)函数时,会首先遍历deps,移除对dep的订阅,然后把newDepIds和depIds交换,newDeps和deps交换,并且把newDepsIds和newDeps清空,在条件渲染时,即使对不用渲染的数据订阅移除,减少性能浪费。
      由于Vue是数据驱动的,所以在每次数据变化都会重写Render,那么vm._render()方法会再次执行,并再次触发数据
      收集依赖的目的是为了当这些响应式数据发生变化时,触发它们的setter的时候,能知道应该通过哪些订阅者去做相应的逻辑处理(也就是派发更新)
  • 派发更新(setter)
    childOb = !shallow &&observe(newVal)  
    //如果shallow为false的情况,会对新设置的值变成一个响应式对象
    
    dep.notify()
    // 通知所有订阅者
    
  • 派发过程
      当我们组件中对相应的数据做了修改,就会触发setter的逻辑,最后调用dep.notify()方法,它是Dep的一个实例方法。具体的做法是遍历依赖收集中建立的subs,也就是Watcher的实例数组。subs数组在依赖收集getter中被添加,期间会通过一些逻辑处理判断保证同意数组不会被添加多次,然后调用每一个watcher的update方法.
      update函数中有多个queueWatcher(this)方法引入了队列的概念,是vue在做派发更新时优化的一个点,它并不会每次数据改变都会触发watcher回调,而是把watcher先添加到一个队列中,然后在nextTick后执行watcher的run函数
    • 队列排序保证
    1. 组件的=更新由父到子,父组件的创建要早于子组件,watcher的创建也是如此
    2. 自定义的watcher要早于watcher执行,原因是自定义的watcher是在渲染watcher前创建的
    3. 队列遍历:排序完成后,对队列进行遍历,拿到对应的watcher,执行watcher.run()
    • run函数解析
        先通过this.get()得到它的当前值,然后做判断,如果满足新旧值不相等、新值是对象类型、deep模式任何一个条件,则执行watcher的回调,注意回调函数执行时会把第一个参数和第二个参数传入新值value和旧值oldValue,这就是当我们自己添加watcher时可以在参数中取得新旧值的来源。对应渲染watcher而言,在执行this.get()方法求值的时候会执行getter方法。因此,修改组件相关数据的时候,会触发组件重新渲染,接着执行patch的过程。

对本人来说,如此篇幅的纯文字博文还是头一次,难免会有一些语言表达不规范和难以阅读之处,希望大家可以多多包涵。没有代码的博文不是一篇好博文,下面给各位老铁表演一个手写数据绑定。

<input id="input" type="text">
<div id="text"></div>
let data={value:''};
Object.defineProperty(data,'value',{
    set:function(val){
        $("#text").innerHTML = val;
        $("#input").value = val;
    },
    get:function(val){
        return $("#input").value;
    }
})

$("#input").onkeyup = function(e){
    data.value=e.target.value;
}

function $(str){
    if(str.charAt(0)=="#"){
        return document.getElementById(str.substr(1));
    }else if(str.charAt(0)=="."){
        return document.getElementsByClassName(str.substr(1));
    }else{
        return document.getElementsByTagName(str.substr(1));
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值