一套代码小程序&Web&Native运行的探索04——数据更新

接上文:一套代码小程序&Web&Native运行的探索03javascript

对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/mvvmhtml

参考:vue

https://github.com/fastCreator/MVVM(极度参考,十分感谢该做者,直接看Vue会比较吃力的,可是看完这个做者的代码便会轻易不少,惋惜这个做者没有对应博客说明,否则就爽了)java

https://www.tangshuang.net/3756.htmlnode

https://www.cnblogs.com/kidney/p/8018226.htmlgit

http://www.cnblogs.com/kidney/p/6052935.htmlgithub

https://github.com/livoras/blog/issues/13小程序

以前咱们完成了简陋的从模板到虚拟DOM从虚拟DOM到HTML的代码,咱们这里图简单没有对属性和样式作特殊处理,仍是按照通常的模板方式进行的解析,后续看看这块怎么处理吧,今天咱们的任务是完成setData时候同步更新咱们的HTML的操做,这里首先咱们来看看通常的MVVM中数据变化更新是怎么完成的,在这个基础上进行后续的代码可能各位看得更清晰。app

通常的MVVM双向绑定框架

通常来讲,咱们数据变化的时候都是一个发布订阅模式,咱们调用setData的时候会执行相似这样的代码:

1 function setData(data) {
2   //作下数据变动
3   //......
4 
5   //会通知对应数据对象数据发生变化了,这个数据对应的全部dom节点都会发生改变
6   this.notifyAll();
7 }

而在vue中咱们是直接作这种操做,dom就发生了变化:

this.name = '叶小钗';

这个是由于,他使用了访问器属性:

 1 var obj = { };
 2 // 为obj定义一个名为 name 的访问器属性
 3 Object.defineProperty(obj, "name", {
 4 
 5   get: function () {
 6     console.log('get', arguments);
 7   },
 8   set: function (val) {
 9     console.log('set', arguments);
10   }
11 })
12 obj.name = '叶小钗'
13 console.log(obj, obj.name)
14 /*
15  set Arguments ["叶小钗", callee: ƒ, Symbol(Symbol.iterator): ƒ]
16  get Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]
17 */

若是这里写这样的代码:

 1 <div id="a">
 2 </div>
 3 <input type="text" id="b">
 4 
 5 <script type="text/javascript" >
 6 
 7 function setData(data) {
 8   //作下数据变动
 9   //......
10   //会通知对应数据对象数据发生变化了,这个数据对应的全部dom节点都会发生改变
11   this.notifyAll();
12 }
13 
14 function getElById(id) {
15   return document.getElementById(id);
16 }
17 
18 var obj = {};
19 // 为obj定义一个名为 name 的访问器属性
20 Object.defineProperty(obj, "name", {
21   set: function (val) {
22     getElById('a').innerHTML = val;
23     getElById('b').value = val;
24   }
25 })
26 
27 getElById('b').addEventListener('input', function(e) {
28   obj.name = e.target.value;
29 });
30 
31 </script>

文本框中的字符串和div的便会同步更新,这个即是最简化的双向绑定代码了,真实状况下咱们的代码多是这样的:

① 将data中的数据(这里是name属性),与两个dom对象进行映射一个是input另外一个是空字符串(能够想象为span)

② 当data中name字段发生变化,或者view中致使name发生变化(控制台或者事件监听)

③ data数据变化时,文本节点同步发生变化(无论是控制台js脚本致使仍是输入变化)

PS:咱们这里与小程序保持一致,真正作更新时候采用setData方法进行

这里便开始引入编译过程:

 1 <div id="app">
 2   <input type="text" v-model="name">
 3   {{name}}
 4 </div>
 5 
 6 <script type="text/javascript" >
 7 
 8   function getElById(id) {
 9     return document.getElementById(id);
10   }
11 
12   //这块代码仅作功能说明,不用当真
13   function compile(node, vm) {
14     let reg = /\{\{(.*)\}\}/;
15 
16     //节点类型
17     if(node.nodeType === 1) {
18       let attrs = node.attributes;
19       //解析属性
20       for(let i = 0, l = attrs.length; i < l; i++) {
21         if(attrs[i].nodeName === 'v-model') {
22           let name = attrs[i].nodeValue;
23           node.value = vm.data[name] || '';
24           //此处不作太多判断,直接绑定事件
25           node.addEventListener('input', function (e) {
26             //赋值操做
27             let newObj = {};
28             newObj[name] = e.target.value;
29             vm.setData(newObj);
30           });
31 
32           break;
33         }
34       }
35     } else if(node.nodeType === 3) {
36 
37         if(reg.test(node.nodeValue)) {
38           let name = RegExp.$1; // 获取匹配到的name
39           name = name.trim();
40           node.nodeValue = vm.data[name] || '';
41         }
42     }
43   }
44 
45   //获取节点
46   function nodeToFragment(node, vm) {
47     let flag = document.createDocumentFragment();
48     let child;
49 
50     while (child = node.firstChild) {
51       compile(child, vm);
52       flag.appendChild(child);
53     }
54 
55     return flag;
56   }
57 
58   function MVVM(options) {
59     this.data = options.data;
60     let el = getElById(options.el);
61     this.$dom = nodeToFragment(el, this)
62     this.$el = el.appendChild(this.$dom);
63 
64 //    this.$bindEvent();
65   }
66 
67   MVVM.prototype.setData = function (data) {
68     for(let k in data) {
69       this.data[k] = data[k];
70     }
71     //执行更新逻辑
72   }
73 
74   let mvvm = new MVVM({
75     el: 'app',
76     data: {
77       name: '叶小钗'
78     }
79   })
80 
81 </script>

这个时候input输入更改,对应属性也会发生变化,可是咱们属性发生变化并无引发全部的dom发生变化,这个是不对的,这里咱们便须要劫持全部的数据对象,这里引入发布订阅模式:

  1 <div id="app">
  2   <input type="text" v-model="name">
  3   {{name}}
  4 </div>
  5 
  6 <script type="text/javascript" >
  7 
  8   function getElById(id) {
  9     return document.getElementById(id);
 10   }
 11 
 12   //主体对象,存储全部的订阅者
 13   function Dep () {
 14     this.subs = [];
 15   }
 16 
 17   //通知全部订阅者数据变化
 18   Dep.prototype.notify = function () {
 19     for(let i = 0, l = this.subs.length; i < l; i++) {
 20       this.subs[i].update();
 21     }
 22   }
 23 
 24   //添加订阅者
 25   Dep.prototype.addSub = function (sub) {
 26     this.subs.push(sub);
 27   }
 28 
 29   let globalDataDep = new Dep();
 30 
 31   //观察者,框架会接触data的每个与node相关的属性,
 32   //若是data没有与任何节点产生关联,则不予理睬
 33   //实际的订阅者对象
 34   //注意,只要一个数据对象对应了一个node对象就会生成一个订阅者,因此真实通知的时候应该须要作到通知到对应数据的dom,这里不予关注
 35   function Watcher(vm, node, name) {
 36     this.name = name;
 37     this.node = node;
 38     this.vm = vm;
 39     if(node.nodeType === 1) {
 40       this.node.value = this.vm.data[name];
 41     } else if(node.nodeType === 3) {
 42       this.node.nodeValue = this.vm.data[name] || '';
 43     }
 44     globalDataDep.addSub(this);
 45 
 46   }
 47 
 48   Watcher.prototype.update = function () {
 49     if(this.node.nodeType === 1) {
 50       this.node.value = this.vm.data[this.name ];
 51     } else if(this.node.nodeType === 3) {
 52       this.node.nodeValue = this.vm.data[this.name ] || '';
 53     }
 54   }
 55 
 56   //这块代码仅作功能说明,不用当真
 57   function compile(node, vm) {
 58     let reg = /\{\{(.*)\}\}/;
 59 
 60     //节点类型
 61     if(node.nodeType === 1) {
 62       let attrs = node.attributes;
 63       //解析属性
 64       for(let i = 0, l = attrs.length; i < l; i++) {
 65         if(attrs[i].nodeName === 'v-model') {
 66           let name = attrs[i].nodeValue;
 67           if(node.value === vm.data[name]) break;
 68 
 69 //          node.value = vm.data[name] || '';
 70           new Watcher(vm, node, name)
 71 
 72           //此处不作太多判断,直接绑定事件
 73           node.addEventListener('input', function (e) {
 74             //赋值操做
 75             let newObj = {};
 76             newObj[name] = e.target.value;
 77             vm.setData(newObj, true);
 78           });
 79 
 80           break;
 81         }
 82       }
 83     } else if(node.nodeType === 3) {
 84 
 85         if(reg.test(node.nodeValue)) {
 86           let name = RegExp.$1; // 获取匹配到的name
 87           name = name.trim();
 88 //          node.nodeValue = vm.data[name] || '';
 89           new Watcher(vm, node, name)
 90         }
 91     }
 92   }
 93 
 94   //获取节点
 95   function nodeToFragment(node, vm) {
 96     let flag = document.createDocumentFragment();
 97     let child;
 98 
 99     while (child = node.firstChild) {
100       compile(child, vm);
101       flag.appendChild(child);
102     }
103 
104     return flag;
105   }
106 
107   function MVVM(options) {
108     this.data = options.data;
109     let el = getElById(options.el);
110     this.$dom = nodeToFragment(el, this)
111     this.$el = el.appendChild(this.$dom);
112 
113 //    this.$bindEvent();
114   }
115 
116   MVVM.prototype.setData = function (data, noNotify) {
117     for(let k in data) {
118       this.data[k] = data[k];
119     }
120     //执行更新逻辑
121 //    if(noNotify) return;
122     globalDataDep.notify();
123   }
124 
125   let mvvm = new MVVM({
126     el: 'app',
127     data: {
128       name: '叶小钗'
129     }
130   })
131 
132 </script>

mvvm.setData({name: 'hello world'})

这段短短的代码,基本将数据变化如何引发的dom变化说的比较清楚了,几个关键流程是:

① 设置全局的发布订阅模式

② 在模板编译的时候,一旦碰到数据节点与dom节点发生关系时,则新增一个订阅者,咱们这里的发布者没有状态概念,真实的状况应该是以data为一个集合的分组,这样能够作到安data进行更新

③ 数据变化时候执行setData,底层调用发布者除非对应订阅者更新数据,这里只是简单的属性&文本更新,真实状况会复杂的多,咱们这里为保持小程序逻辑,没有实现访问器属性部分代码

有了以上代码的理解,咱们再回到咱们昨天的代码继续完成这个流程便会清晰的多

完成setData代码

根据以前的学习,咱们知道添加订阅者必定是发生在编译时期,data跟node产生关联的时候,可是咱们这里须要发布订阅者相关代码,因为咱们这里的诉求还要简单一些并不想去考虑属性样式这些特殊性,因此咱们对TextParser作点改造,先实现之:

注意这里的核心是,每次数据改变的时候都会触发观察者的update,这样会引发从新生成虚拟树(vnode),可是到底要不要从新渲染,怎么渲染后面会直接由snabbdom接手,咱们只是将这种关系完成,代码比较分散你们能够到github上面看:https://github.com/yexiaochai/wxdemo/tree/master/mvvm

而后今天的学习到此为止,咱们明天开始处理事件部分的代码,感受代码逐渐有些慢了,等组件部分完成后咱们画点流程图从新梳理下逻辑

相关文章
相关标签/搜索