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

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

参考:

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

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

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

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

https://github.com/livoras/blog/issues/13

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

一般的MVVM双向绑定

一般来说,我们数据变化的时候都是一个发布订阅模式,我们调用setData的时候会执行类似这样的代码:

 function setData(data) {
//做下数据变更
//...... //会通知对应数据对象数据发生变化了,这个数据对应的所有dom节点都会发生改变
this.notifyAll();
}

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

this.name = '叶小钗';

这个是因为,他使用了访问器属性:

 var obj = { };
// 为obj定义一个名为 name 的访问器属性
Object.defineProperty(obj, "name", { get: function () {
console.log('get', arguments);
},
set: function (val) {
console.log('set', arguments);
}
})
obj.name = '叶小钗'
console.log(obj, obj.name)
/*
set Arguments ["叶小钗", callee: ƒ, Symbol(Symbol.iterator): ƒ]
get Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]
*/

如果这里写这样的代码:

 <div id="a">
</div>
<input type="text" id="b"> <script type="text/javascript" > function setData(data) {
//做下数据变更
//......
//会通知对应数据对象数据发生变化了,这个数据对应的所有dom节点都会发生改变
this.notifyAll();
} function getElById(id) {
return document.getElementById(id);
} var obj = {};
// 为obj定义一个名为 name 的访问器属性
Object.defineProperty(obj, "name", {
set: function (val) {
getElById('a').innerHTML = val;
getElById('b').value = val;
}
}) getElById('b').addEventListener('input', function(e) {
obj.name = e.target.value;
}); </script>

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

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

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

③ data数据变化时,文本节点同步发生变化(不管是控制台js脚本导致还是输入变化)

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

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

 <div id="app">
<input type="text" v-model="name">
{{name}}
</div> <script type="text/javascript" > function getElById(id) {
return document.getElementById(id);
} //这块代码仅做功能说明,不用当真
function compile(node, vm) {
let reg = /\{\{(.*)\}\}/; //节点类型
if(node.nodeType === 1) {
let attrs = node.attributes;
//解析属性
for(let i = 0, l = attrs.length; i < l; i++) {
if(attrs[i].nodeName === 'v-model') {
let name = attrs[i].nodeValue;
node.value = vm.data[name] || '';
//此处不做太多判断,直接绑定事件
node.addEventListener('input', function (e) {
//赋值操作
let newObj = {};
newObj[name] = e.target.value;
vm.setData(newObj);
}); break;
}
}
} else if(node.nodeType === 3) { if(reg.test(node.nodeValue)) {
let name = RegExp.$1; // 获取匹配到的name
name = name.trim();
node.nodeValue = vm.data[name] || '';
}
}
} //获取节点
function nodeToFragment(node, vm) {
let flag = document.createDocumentFragment();
let child; while (child = node.firstChild) {
compile(child, vm);
flag.appendChild(child);
} return flag;
} function MVVM(options) {
this.data = options.data;
let el = getElById(options.el);
this.$dom = nodeToFragment(el, this)
this.$el = el.appendChild(this.$dom); // this.$bindEvent();
} MVVM.prototype.setData = function (data) {
for(let k in data) {
this.data[k] = data[k];
}
//执行更新逻辑
} let mvvm = new MVVM({
el: 'app',
data: {
name: '叶小钗'
}
}) </script>

这个时候input输入更改,对应属性也会发生变化,但是我们属性发生变化并没有引起所有的dom发生变化,这个是不对的,这里我们便需要劫持所有的数据对象,这里引入发布订阅模式:

 <div id="app">
<input type="text" v-model="name">
{{name}}
</div> <script type="text/javascript" > function getElById(id) {
return document.getElementById(id);
} //主体对象,存储所有的订阅者
function Dep () {
this.subs = [];
} //通知所有订阅者数据变化
Dep.prototype.notify = function () {
for(let i = 0, l = this.subs.length; i < l; i++) {
this.subs[i].update();
}
} //添加订阅者
Dep.prototype.addSub = function (sub) {
this.subs.push(sub);
} let globalDataDep = new Dep(); //观察者,框架会接触data的每一个与node相关的属性,
//如果data没有与任何节点产生关联,则不予理睬
//实际的订阅者对象
//注意,只要一个数据对象对应了一个node对象就会生成一个订阅者,所以真实通知的时候应该需要做到通知到对应数据的dom,这里不予关注
function Watcher(vm, node, name) {
this.name = name;
this.node = node;
this.vm = vm;
if(node.nodeType === 1) {
this.node.value = this.vm.data[name];
} else if(node.nodeType === 3) {
this.node.nodeValue = this.vm.data[name] || '';
}
globalDataDep.addSub(this); } Watcher.prototype.update = function () {
if(this.node.nodeType === 1) {
this.node.value = this.vm.data[this.name ];
} else if(this.node.nodeType === 3) {
this.node.nodeValue = this.vm.data[this.name ] || '';
}
} //这块代码仅做功能说明,不用当真
function compile(node, vm) {
let reg = /\{\{(.*)\}\}/; //节点类型
if(node.nodeType === 1) {
let attrs = node.attributes;
//解析属性
for(let i = 0, l = attrs.length; i < l; i++) {
if(attrs[i].nodeName === 'v-model') {
let name = attrs[i].nodeValue;
if(node.value === vm.data[name]) break; // node.value = vm.data[name] || '';
new Watcher(vm, node, name) //此处不做太多判断,直接绑定事件
node.addEventListener('input', function (e) {
//赋值操作
let newObj = {};
newObj[name] = e.target.value;
vm.setData(newObj, true);
}); break;
}
}
} else if(node.nodeType === 3) { if(reg.test(node.nodeValue)) {
let name = RegExp.$1; // 获取匹配到的name
name = name.trim();
// node.nodeValue = vm.data[name] || '';
new Watcher(vm, node, name)
}
}
} //获取节点
function nodeToFragment(node, vm) {
let flag = document.createDocumentFragment();
let child; while (child = node.firstChild) {
compile(child, vm);
flag.appendChild(child);
} return flag;
} function MVVM(options) {
this.data = options.data;
let el = getElById(options.el);
this.$dom = nodeToFragment(el, this)
this.$el = el.appendChild(this.$dom); // this.$bindEvent();
} MVVM.prototype.setData = function (data, noNotify) {
for(let k in data) {
this.data[k] = data[k];
}
//执行更新逻辑
// if(noNotify) return;
globalDataDep.notify();
} let mvvm = new MVVM({
el: 'app',
data: {
name: '叶小钗'
}
}) </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

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

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

  1. 一套代码小程序&Web&Native运行的探索05——snabbdom

    接上文:一套代码小程序&Web&Native运行的探索04——数据更新 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/ma ...

  2. 一套代码小程序&Web&Native运行的探索06——组件系统

    接上文:一套代码小程序&Web&Native运行的探索05——snabbdom 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tre ...

  3. 一套代码小程序&Web&Native运行的探索03——处理模板及属性

    接上文:一套代码小程序&Web&Native运行的探索02 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/m ...

  4. 一套代码小程序&Web&Native运行的探索02

    接上文:一套代码小程序&Web&Native运行的探索01,本文都是一些探索性为目的的研究学习,在最终版输出前,内中的内容可能会有点乱 参考: https://github.com/f ...

  5. 一套代码小程序&Web&Native运行的探索07——mpvue简单调研

    前言 接上文:[一套代码小程序&Native&Web阶段总结篇]可以这样阅读Vue源码 最近工作比较忙,加之上个月生了小孩,小情人是各种折腾他爸妈,我们可以使用的独立时间片不多,虽然这 ...

  6. 一套代码小程序&Web&Native运行的探索01

    前言 前面我们对微信小程序进行了研究:[微信小程序项目实践总结]30分钟从陌生到熟悉 并且用小程序翻写了之前一个demo:[组件化开发]前端进阶篇之如何编写可维护可升级的代码 之前一直在跟业务方打交道 ...

  7. 【一套代码小程序&Native&Web阶段总结篇】可以这样阅读Vue源码

    前言 前面我们对微信小程序进行了研究:[微信小程序项目实践总结]30分钟从陌生到熟悉 在实际代码过程中我们发现,我们可能又要做H5站又要做小程序同时还要做个APP,这里会造成很大的资源浪费,如果设定一 ...

  8. 小程序 web 端实时运行工具

    微信小程序 web 端实时运行工具 https://chemzqm.github.io/wept/

  9. 小程序web开发框架-weweb介绍

    weweb是一个兼容小程序语法的前端框架,你可以用小程序的写法,来写web单面应用.如果你已经有小程序了,通过它你可以将你的小程序运行在浏览器中.在小程序大行其道的今天,它可以让你的小程序代码得到最大 ...

随机推荐

  1. I/O-----二进制文件的读写

    好吧  已经被I/O刷屏了 这是复制文件 DataInputStream  dis =new DataInputStream(new FileInputStream("src/pcl.jpg ...

  2. WPF 列表开启虚拟化的方式

    正确开启虚拟化的方式 列表如ListBox,ListView,TreeView,GridView等,开启虚拟化 ScrollViewer设置CanContentScroll=True 直接在模板中,设 ...

  3. Java 11 新功能来了!

    关键时刻,第一时间送达! 目前 Oracle 已经发布了 Java Development Kit 10,下个版本 JDK 11 也即将发布.本文介绍 Java 11 的新功能. 根据Oracle新出 ...

  4. 利用face_recognition库裁取人脸

    from PIL import Image import face_recognition # Load the jpg file into a numpy array image = face_re ...

  5. ERP不规范,同事两行泪

    最近的很多次对外交流,都聊到了ERP建设的话题,并且无一例外的不那么让人省心,回想我这么多年走过的ERP坑坑路,在这里也写下经验和总结,希望能给正在或者即将走上ERP建设路的企业一些思考和帮助. 导读 ...

  6. 兄弟俩畅游Tomcat城市的SpringMVC科技园区

    Tomcat城市 Tomcat这座城市的历史相当悠久了,经历过几次大的变迁后,呈现出非常明显的地域特征. 从城市往西走,过了城乡结合部以后,可以说是满目疮痍.一片破败,这就是Servlet地区,这座城 ...

  7. spring boot 2.0 Feign的客户端

    1.pom.xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId ...

  8. 《ASP.NET MVC 5 高级编程》学习笔记

    前言: 记得当初培训的时候,学习的还是ASP.NET,现在回想一下,图片水印.统计人数.过滤器....HttpHandler是多么的经典! 不过后来接触到了MVC,便立马爱上了它.Model-View ...

  9. Visual Studio Code-批量在文末添加文本字段

    小技巧一例,在vs code或notepad++文末批量添加文本字段信息,便于数据信息的完整,具体操作如下: Visual Studio Code批量添加"@azureyun.com&quo ...

  10. ambari2.6.50 openssl 版本问题:SSLError: Failed to connect. Please check openssl library versions. Openssl error upon host registration

    I'm trying to register hostnames in Ambari but getting the error below. We tried to run yum update o ...