Vue双向绑定原理(源码解析)---getter setter
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的更多相关文章
- vue双向绑定原理源码解析
当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/maxlove123 ...
- vue双向绑定的原理及实现双向绑定MVVM源码分析
vue双向绑定的原理及实现双向绑定MVVM源码分析 双向数据绑定的原理是:可以将对象的属性绑定到UI,具体的说,我们有一个对象,该对象有一个name属性,当我们给这个对象name属性赋新值的时候,新值 ...
- [Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅
有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫 ...
- vue双向绑定原理分析
当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/jiangzhenf ...
- vue双向绑定原理及实现
vue双向绑定原理及实现 一.总结 一句话总结:vue中的双向绑定主要是通过发布者-订阅者模式来实现的 发布 订阅 1.单向绑定和双向绑定的区别是什么? model view 更新 单向绑定:mode ...
- Vue双向绑定原理,教你一步一步实现双向绑定
当今前端天下以 Angular.React.vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋. 所以我们要时刻保持好奇心,拥抱变化,只有在不断的变 ...
- vue 学习二 深入vue双向绑定原理
vue双向绑定原理 请示总体来讲 就是为data的中的每个属性字段添加一个getter/seter属性 以此来追踪数据的变化,而执行这部操作,依赖的就是js的Object.defineProperty ...
- Laya Timer原理 & 源码解析
Laya Timer原理 & 源码解析 @author ixenos 2019-03-18 16:26:38 一.原理 1.将所有Handler注册到池中 1.普通Handler在handle ...
- Vue.js 2.0源码解析之前端渲染篇
一.前言 Vue.js框架是目前比较火的MVVM框架之一,简单易上手的学习曲线,友好的官方文档,配套的构建工具,让Vue.js在2016大放异彩,大有赶超React之势.前不久Vue.js 2.0正式 ...
随机推荐
- CSS - 内联元素span 强制换行失败的可能原因
在CSS中,标签span 强制换行失败:(使用display:block) 可能原因:float:left or float:right
- js通过String取得对应全局Object的值
//假设有个全局对象Person var Person = { 'name' : 'alice' } //通过某种配置,获得了字符串形式的对象名 var thisPerson = 'Person'; ...
- redis 在 Linux 和 Windows 上的安装配置
最近需要在服务器上安装 redis,虽然只是一个小事情,但这个过程中也遇到了不少的问题,所以做一个总结,也希望能给到其他人一些帮助. 本文记录了 linux 系统和 windows 系统的 redis ...
- WEB服务器(Tomcat)
在小型的应用系统或有特殊需要的系统中,也可以使用一个免费的Web服务器: Tomcat,该服务器支持全部的JSP以及Servlet 规范, 下载 Tom 查看计算机上被占用端口号的情况: 使用Fpor ...
- 定时任务为什么不用Timer
在做定时任务的时候,有的同学可能能会用到Timer这个定时任务的辅助类, 可是使用它会有潜在的风险,风险例如以下, (1)时间计算不准确问题 由于Timer是以绝对时间计算定时任务的,会受到系 ...
- android优化 清除无效代码 UCDetector
android下优化 清除无效 未被使用的 代码 UCDetector 官方下载地址:http://www.ucdetector.org/index.html UCDetector 是 eclips ...
- html5开发手机打电话发短信功能,html5的高级开发,html5开发大全,html手机电话短信功能具体解释
在非常多的手机站点上,有打电话和发短信的功能,对于这些功能是怎样实现的呢.事实上不难,今天我们就用html5来实现他们. 简单的让你大开眼界.HTML5 非常easy写,但创建网页时,您常常须要反复做 ...
- oracle性能检测sql语句
1. 监控事例的等待 select event,sum(decode(wait_Time,0,0,1)) "Prev",sum(decode(wait_Time,0,1,0)) & ...
- 杂项:WCF
ylbtech-杂项:WCF Windows Communication Foundation(WCF)是由微软开发的一系列支持数据通信的应用程序框架,可以翻译为Windows 通讯开发平台. 整合了 ...
- java类List及List遍历器的代码
从某个程序中截取的一个示例代码: List<User> users = userDao.selectAll(); //mybatis java orm Iterator<User&g ...