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正式 ...
随机推荐
- [tyvj-1391]走廊泼水节 最小生成树
做克鲁斯卡尔的时候维护一个并查集即可. #include <iostream> #include <cstdio> #include <cstring> #incl ...
- 《你又怎么了我错了行了吧》【Alpha】Scrum meeting 2
第二天 日期:2019/6/15 前言: 第2次会议在9C-405召开 进行第一天工作的检查,开始第二天工作的安排和学习 1.1 今日完成任务情况以及明天任务安排 姓名 当前阶段任务 下一阶段任务 刘 ...
- 《黑白团团队》第八次团队作业:Alpha冲刺 第五天
项目 内容 作业课程地址 任课教师首页链接 作业要求 团队项目 填写团队名称 黑白团团队 填写具体目标 认真负责,完成项目 团队项目Github仓库地址链接. 第五天 日期:2019/6/19 成员 ...
- base64 编码的作用及原理
Base64编码的作用:由于某些系统中只能使用ASCII字符.Base64就是用来将非ASCII字符的数据转换成ASCII字符的一种方法.它使用下面表中所使用的字符与编码. 而且base64特别适合在 ...
- 关于参数net_buffer_length How MySQL Uses Memory
http://dev.mysql.com/doc/refman/5.6/en/memory-use.html The following list indicates some of the ways ...
- PatentTips - Controlling TSC offsets for multiple cores and threads
BACKGROUND Many processors include a time stamp count (TSC) counter which is typically implemented a ...
- 0111mysql如何选择Join的顺序
本文通过一个案例来看看MySQL优化器如何选择索引和JOIN顺序.表结构和数据准备参考本文最后部分"测试环境".这里主要介绍MySQL优化器的主要执行流程,而不是介绍一个优化器的各 ...
- 第18题 Remove Element
Given an array and a value, remove all instances of that value in place and return the new length. T ...
- Error: CompareBaseObjectsInternal can only be called from the main thread
Posted: 01:39 PM 06-17-2013 hi, we're working on a project where we need to do some calculations on ...
- Unity特殊目录和脚本编译顺序
特殊目录和脚本编译顺序 大多数情况下,您能够选择不论什么你喜欢的目录在您的项目的名称.但unity储备一些名称以指示内容有一个特殊的用途.这些目录中有些会影响脚本编译的顺序.从根本上说,有四个单 ...