reference:
<div id="app>
<input type="text" v-model="text">
{{text}}
</div>
<script>
const vm=new Vue({
el: 'app',
data: {
text: 'hello'
}
});
</script>
1. 我们先来完成第一步,也就是model=>view
这一层的渲染。
function updateNode(node,vm){
var frag=document.createDocumentFrag();
var child=null;
while(child=node.firstChild){
compile(child,vm);
frag.appendChild(child);
}
//这里我们可以观察到一个细节:在所有的子节点都更新完成之后,再去更新dom
node.append(frag);
}
function compile(node,vm){
var reg=/\{\{(.*)\}\}/
if(node.nodeType==1){
var attrs=node.attrbutes; // a nodeList
for(i=0;i<attrs.length;i++){
if(attrs[i].nodeName=='v-model'){
node.value=vm.data[attrs[i].nodeValue];
node.removeAttribute('v-model');
}
}
}
if(node.nodeType==3){
if(reg.test(node.nodeValue){
var name=RegExp.$1;
name=name.trim();
node.nodeValue=vm.data[name];
}
}
}
//Vue constructor
function Vue(options){
var id=options.el;
this.data=options.data;
updateNode(document.getElementById(id),this);
}
搞定!是不是很简单!
2. 下面我们来看第二步:view=>model
几个需要预先领会的point:
- 这里我们把
vm
对象中的属性用Object.defineProperty()
来定义。vm.data[property1]=newVal
这个赋值语句执行的时候,会调用set()
函数,我们就可以在set
函数里进行操作。 - 发布订阅模式
var publisher={
publish: function(){
dep.notify();
}
}
sub1={update:function(){...}};
sub1={update:function(){...}};
sub1={update:function(){...}};
function Dep{
this.subs=[sub1,sub2,sub3];
}
Dep.prototype.notify=function(){
this.subs.forEach(sub=>{
sub.update();
)
}
发布订阅模式与双向绑定的关系:我们为vue实例data
中的每个属性都分配一个dep
对象;同时在编译html
的时候,把与data
相关的节点分配一个watcher
对象。这里的关键在于,如果把节点对应的watcher
对象添加到dep
中。
我们需要在watcher
的构造函数里做这些事:
function Watcher(vm,node,name){
Dep.target=this;
this.name=name;
this.vm=vm;
this.update();
Dep.target=null;
}
Watcher.prototype={
update: function(){
this.get();
this.node.nodeValue=this.value;
}
get: function(){
this.value=this.vm[this.name];
}
}
compile()
函数有关的部分:我们为v-model
元素添加事件,并且为data属性对应的节点生成对应的watcher
对象。
function(node,vm){
if(node.nodeType==1){
var attrs=node.attributes;
for(var i=0;i<attrs.length;i++){
if(attrs[i].nodeName='v-model'){
node.addEventListener('change',e=>vm[attrs[i].nodeValue]=e.target.value)
}
}
}
if(node.nodeType==3){
if(reg.test(node.nodeValue)){
var name=RegExp.$1;
name=name.trim();
new Watcher(node,vm,name);
}
}
}
vm
定义data
中某个特性:
function defineReactive(obj,key,val){
var dep=new Dep();
Object.defineProperty(obj,keyName,{
get: function(){
if(Dep.target) dep.addSub(Dep.target);
}
set: function(newVal){
if(newVal==val) return;
val=newVal;
dep.notify();
}
}
}
双向绑定大致的process整理:
- 创建某一个
vue
实例,vue
实例中data
中的每个属性都会创建一个dep
对象与之对应; -
vue
实例编译对应的html
模板,某一个节点如果用到data
的某一个属性,就会创建一个它自己的watcher
对象,在这个构造函数里,读取对应的data
值,更新视图,同时这个过程调用了get
函数,所以会将watch
对象添加到dep
对象中。 - 当每一次
data
属性更新的时候,set
函数会调用notify
函数,通知dep中的每个成员对象更新。