双向数据绑定,我们首先来看数据改变如何触发页面的刷新。
首先通过Object.defineProperty( )对vue的data设置getter和setter。实现拦截数据。我们的数据都是在html里以指令或者变量的形式存在。
一个笨办法
遍历全部DOM,把需要更新数据的DOM都和对应的数据映射起来,存在在一个对象中,就是一个数据一个对象。然后全部放在一个数组中。每当有数据变化,就去遍历这个数组,把对应数据的DOM进行更新。这么做的缺点是每次都需要遍历全部的数组。
别的办法
一一对应的关系,是不是该用对象?想想node里路由传参数的那个问题。我们一个对象例如为BInging:{},key是data的每个值,value是依赖这个数据的DOM。然后数据变化BInging[变化的数据]就可以取出value更新相关的DOM。
vue的方法
vue中并没有专门设置这样一个对象。而是把抵赖放入了data每个值得getter函数中。更新放进了setter中。就是吧独立出来的BInging对象和data对象进行了融合。
因为这里的get和set会因为闭包造成作用域不被回收,所以dep会保存在每个data的get和set作用域中。
v-model更新数据
给v-model加上input监听事件。触发事件的时候去更新data的值。视图自然就跟着变了。
实现双向绑定MVVM
vue的双向绑定原理及实现
vue双向绑定数据原理
我们的目录结构为
compile.js // 解析vue的DOM
dep.js // 增加监听数据和Watcher中间的桥梁
index.js // 初始化操作
observer.js // 对data数据进行劫持
watcher // 数据变化需要通知的订阅者
首先我们给vue的data的所以数据用Object.defineProperty
其次我们来解析DOM。
采用递归方法从vue传入的el(一般为el: '#app'
,)根元素进行向下遍历。
目前我们先只考虑文本节点。只是把{{}}等解析成页面展示的内容放入页面内比较简单。
现在如果data的数据变化,页面的内容也需要变化。这样改怎样做?
一个数据例如data
下的name
变化肯能页面有多处用到这个变量。所以要把所有用到name
变量的地方都进行更新。我们每个data
有一个dep
对象。用来存放所有的依赖。这就需要我们。变量DOM
的时候,碰到name
就把相应的dom
更新函数存放到name
的dep
中。在什么位置进行dep
的存放呢?第一次变量DOM
肯定会触发变量的get
。我们放到get函数中。
vue中,比较不好理解的就是Dep.target。为什么需要Dep.target
var uid = 0;
function Dep() {
this.id = uid++;
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
depend: function() {
Dep.target.addDep(this);
},
removeSub: function(sub) {
var index = this.subs.indexOf(sub);
if (index != -1) {
this.subs.splice(index, 1);
}
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
Dep.target = null; //
只有获取数据就会触发get
,但不能通过手动的alert(dat.name)
这种代码。也给dep
增加依赖吧。这样也没有依赖可以加啊。所以Dep.target
就是为了标志订阅者Watcher
的。就是只有我们new Watcher()
的时候Dep.target
值才为真,才会被添加到dep
中。Dep.target
可以在watcher.js
和observer.js
中通过 import Dep from './dep.js'
来或者或者设置Dep.target
这个变量。为什么不放在Dep构造函数的原型中呢?因为Dep.target 还需要通过watcher.js
进行设置。这个Dep.target
到底是什么有什么用处呢?
import Dep from './dep.js'
function Watcher () {
this.get()
}
get: function() {
Dep.target = this;
var value = this.getter.call(this.vm, this.vm); // 解析DOM中的表达式触发data的get函数。
Dep.target = null;
return value;
},
当触发get函数时候设置Dep.target
,经过判断可以触发dep.depend();
或者dep.addSub(Dep.target)
目的都是为了给dep中增加依赖的watcher。
Dep.prototype = {
depend: function() {
Dep.target.addDep(this); // Dep.target就是刚刚设置的Watcher实例
},
addSub:function() {
this.subs.push(sub)
},
}
dep.depend();
又转而触发了Watcher
的addDep
。为什么呢?
因为我们需要在dep
中添加watcher
实例。dep
实例或者构造函数中并没有watcher
实例啊。所以只能把通过传参数,把一方传到另一方去进行二合一。这里把dep
实例传入到了watcher
实例中。
addDep: function(dep) {
dep.addSub(this);
this.depIds[dep.id] = dep;
},
addDep
的this
为watcher
实例dep
为Dep.target.addDep(this)
的this
也就是dep
实例。
然而发现直接
dep.addSub(Dep.target)
就可以实现把
watcher
添加到
dep
中。因为
Dep.target
就是
watcher
实例。
现在我们的
data
的
dep
添加
watcher
完成,只需要在
data
的
set
函数触发时候。遍历
dep
中的
subs
进行触发就可以达到更新页面的效果
对应关系
简易源码分析1
简易源码分析2
vue中虚拟DOM的原理
虚拟DOM