Vue双向绑定原理     

大部分都知道Vue是采用的是对象的get 和set方法来实现数据的双向绑定的过程,本章将讨论他是怎么利用他实现的。

vue双向绑定其实是采用的观察者模式,get和set方法只是实现观察者模式的切入点,即在我们set的时候向观察者发布消息,执行观察者的操作,get的时候是为实现set能够通知watcher进行相关处理做准备。下面我们来看一下数据初始化的流程;

数据初始化流程:

数据在初始化时,会调用方法 defineReactive 为数据绑定dep对象(以备之后使用),在进行挂载时会实例化一个进行页面更新的watcher($watcher).,该watcher调用渲染函数(上图的第5步),会调用模板里涉及到的属性的get方法,即为每个属性的dep对象加载对应的依赖$watcher,同时在$watcher下备份涉及到的dep对象。数据初始化时主要是调用data下的属性的get方法,在数据更新时才会调用属性的set方法,详细可看下面的代码注释。

/**
* 采用观察者模式进行数据更新的监听,subject为data下的所有属性以及子孙属性等
*
* subject与watcher是多对多的关系
* 一个subject,如属性data.A,可能对于处理页面更新的Watcher,也可能对应$watch函数传入的表达式更新的Watcher对象,或者观察计算属性的Watcher对象等
* 一个更新页面的Watcher会对象data下的所有属性以及子孙属性等
*
*
* */ function defineReactive(
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
} // cater for pre-defined getter/setters
var getter = property && property.get;
if (!getter && arguments.length === 2) {
val = obj[key];
}
var setter = property && property.set;
// 如果 val为对象,创建一个观察者Observe实例ob,先创建一个dep实例,赋值给Observe.dep ,保存实例只val.__ob__属性, 类似val.__ob__ = ob;
var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
var value = getter ? getter.call(obj) : val; //如果属性之前就定义getter方法,则执行getter方法,并返回属性值
if (Dep.target) { // Dep.target为执行更新的watcher对象
dep.depend(); //为该属性添加观察者(订阅者),挂在对应的dep对象的subs:[]属性下,对应的watcher也会存对应的目标关系与watcher.deps与watcher.depIds
if (childOb) {
//如果是对象,为对象添加该Watcher实例,可用于set, 数组改变,计算属性等操作
childOb.dep.depend();
if (Array.isArray(value)) { //如果value是数据 采用递归的方式为为value下的对象成员添加Observe实例属性 __ob__
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter(newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if ("development" !== 'production' && customSetter) {//如果是开发环境,者打印一些警告
customSetter();
}
if (setter) { //如果属性之前就定义了setter方法,则执行setter方法
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify(); // 数据有改变,通知观察者进行操作
}
});
}

数据更新流程: 

  这里将讲解数据的更新流程,如果我重设了数据如data.A = 5;它将出发A属性的set方法,该方法会对比当前设置的值与之前的值是否相等,如果不相等,在出发dep.notify()函数,类似触发更新事件,代用更新操作,该方法会去调用dep对象下所有依赖watcher的update方法,该方法会排除重复的watcher,最终采用微任务或者定时的方式去执行watcher对应的run方法(run方法调用了watcher的get方法),之后将会调用渲染函数,如此又将与初始化的过程过程的第五步一致,调用data下的get方法,同时备份watcher至涉及到的dep对象下,将上次执行备份中涉及到的dep而本次执行没涉及到dep下的依赖当前watcher删除,想见 Watcher.prototype.cleanupDeps函数注释

在初始化以及数据更新工程,都将调用watcher.get方法,为什么执行该方法就能保证以上功能正常执行呢

watcher.get方法会去调用watcher实例的getter,如果是进行页面更新与渲染的watcher,getter方法这是去执行render函数并将render函数生成的vnode进行渲染。再执行render函数时会涉及到调用模板里的data属性。从而触发属性的get方法。那又是采用什么方式保证属性对应的dep是触发对应的watcher?见Watcher.prototype.get注释

 Watcher.prototype.get = function get() {
pushTarget(this); //该函数的作用是,将Dep.target的值推入堆栈中,并将当前的Watcher实例赋值给Dep.target
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
       //如果是深度监听watching,进行递归加载监听观察者
if (this.deep) {
traverse(value);
}
popTarget(); //将堆栈中栈顶的值弹出堆栈并赋值给 Dep.target
this.cleanupDeps();//与上一次的watcher对象依赖下的dep数据对比,清除没有使用的dep对象
}
return value
};

  在以上代码,在初始化的时候执行watcher.get,其中会调用pushTarget(this),将当前的值赋给Dep.target ,即相当于赋给了一个全局变量。之后将执行watcher的this.getter方法,即render函数,在执行这个函数期间Dep.target一直保持为该watcher,以此保证属性下的dep对象都是对应的watcher,如果在执行render期间有其他watcher执行打断当前的执行,也会在执行其他watcher之后恢复该值,执行完this.getter执行popTarget();将从堆栈中取出执行上下文的watcher值并赋给Dep.target。

下面的代码这是用于保证watcher对象与dep对象的正确依赖关系。同事备份执行watcher依赖的dep对象。

由于dep对象用于通知watcher执行相关的操作,所以他们之间会有一个多对多的对应关系。即dep需要通知哪些watcher,watcher又注册到哪些dep对象中,下面的就是保证每次执行之后这个依赖关系都是正确的

 /**
* 备份本次更新的deps对象和depIds,同时将上次Watcher更新依赖的dep实例,但本次Watcher更新更新不依赖的dep实例下依赖Watcher备份数据删除该Watcher实例
*/
Watcher.prototype.cleanupDeps = function cleanupDeps() {
var this$1 = this; var i = this.deps.length;
while (i--) {
// 获取上一次触发Wactcher更新的dep对象--dep
var dep = this$1.deps[i];
// 如果该对象dep在新的watcher依赖下没有则清除dep对象下依赖的该Watcher对象
if (!this$1.newDepIds.has(dep.id)) {
dep.removeSub(this$1);
}
}
var tmp = this.depIds; //将上次更新的depIds缓存
this.depIds = this.newDepIds; //备份本次更新的Watcher对应的deps对象的id
this.newDepIds = tmp; //将上次更新的depId赋值给newDepIds,并在下一行进行清空
this.newDepIds.clear(); //将newDepIds清空,以备下次更新使用
tmp = this.deps; //deps 对象的备份与 depIds逻辑一致
this.deps = this.newDeps; //备份本次更新的Watcher对应的deps对象
this.newDeps = tmp;
this.newDeps.length = 0; //将newDeps清空,以备下次更新使用
}; 

Vue双向绑定原理(源码解析)---getter setter的更多相关文章

  1. vue双向绑定原理源码解析

    当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/maxlove123 ...

  2. vue双向绑定的原理及实现双向绑定MVVM源码分析

    vue双向绑定的原理及实现双向绑定MVVM源码分析 双向数据绑定的原理是:可以将对象的属性绑定到UI,具体的说,我们有一个对象,该对象有一个name属性,当我们给这个对象name属性赋新值的时候,新值 ...

  3. [Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅

    有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫 ...

  4. vue双向绑定原理分析

    当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/jiangzhenf ...

  5. vue双向绑定原理及实现

    vue双向绑定原理及实现 一.总结 一句话总结:vue中的双向绑定主要是通过发布者-订阅者模式来实现的 发布 订阅 1.单向绑定和双向绑定的区别是什么? model view 更新 单向绑定:mode ...

  6. Vue双向绑定原理,教你一步一步实现双向绑定

    当今前端天下以 Angular.React.vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋. 所以我们要时刻保持好奇心,拥抱变化,只有在不断的变 ...

  7. vue 学习二 深入vue双向绑定原理

    vue双向绑定原理 请示总体来讲 就是为data的中的每个属性字段添加一个getter/seter属性 以此来追踪数据的变化,而执行这部操作,依赖的就是js的Object.defineProperty ...

  8. Laya Timer原理 & 源码解析

    Laya Timer原理 & 源码解析 @author ixenos 2019-03-18 16:26:38 一.原理 1.将所有Handler注册到池中 1.普通Handler在handle ...

  9. Vue.js 2.0源码解析之前端渲染篇

    一.前言 Vue.js框架是目前比较火的MVVM框架之一,简单易上手的学习曲线,友好的官方文档,配套的构建工具,让Vue.js在2016大放异彩,大有赶超React之势.前不久Vue.js 2.0正式 ...

随机推荐

  1. ubuntu重置root密码(转载自https://zhinan.sogou.com/guide/detail/?id=316512881651)

    ubuntu忘记root密码怎么办?如果普通用户忘记了怎么办 ### 第一种方法:无论你是否申请了root帐号,或是普通账号密码忘记了都没有问题的! 1. 重启ubuntu,随即长按shift进入gr ...

  2. easy-ui采坑事件

    新用户首次登陆修改密码 imput标签中使用easyui自带的class="easyui-passwordbox"可以是密码隐藏变成黑点但是无法禁用输入法,然后果断的加了一个typ ...

  3. K - The Unique MST

    K - The Unique MST #include<iostream> #include<cstdio> #include<cstring> #include& ...

  4. 拷贝构造函数(深拷贝vs浅拷贝)

    拷贝构造函数(深拷贝vs浅拷贝) 类对象之间的初始化是由类的拷贝构造函数完毕的.它是一种特殊的构造函数,它的作用是用一个已知的对象来初始化还有一个对象.假设在类中没有显式地声明一个拷贝构造函数.那么, ...

  5. 验证DG最大性能模式下使用ARCH/LGWR及STANDBY LOG的不同情况

    总结:  --两台单实例数据库做DG,数据库版本号10.2.0.1.0 1.主库配置为:arch async,备库无STANDBY LOG. 日志中会有:RFS[4]: No standby redo ...

  6. USACO holstein 超时代码

    /* ID:kevin_s1 PROG:holstein LANG:C++ */第八组数据跪了.半天都不出结果 #include <iostream> #include <cstdi ...

  7. WebGL 权威资源站小聚

    WebGL 权威资源站小聚 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句 ...

  8. HTML5游戏实战之20行代码实现打地鼠

    之前写过一篇打地鼠的博客70行的代码实现打地鼠游戏,细致思考过后,发现70行代码都有点多余了,应用tangide的控件特性,能够将代码量缩减到20行左右. 先show一下终于成果,点击试玩:打地鼠.或 ...

  9. Chrome下使用百度地图报错Cannot read property 'minZoom' of undefined

    问题:工作中在Google chome下面的js console里面测试百度地图API var map = new BMap.Map("container"); map.cente ...

  10. Linux学习之基本介绍

    技术不分年龄高低,只分水平高低. 搞技术25k以下是不看天赋的,25k以上是要看天赋的. 1U服务器,2U服务器,刀片服务器.程序都是运行在服务器上的. 榜样的力量是无穷的.--MK. 汇编语言跟硬件 ...