接上文:一套代码小程序&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. Dubbo中SPI扩展机制解析

    dubbo的SPI机制类似与Java的SPI,Java的SPI会一次性的实例化所有扩展点的实现,有点显得浪费资源. dubbo的扩展机制可以方便的获取某一个想要的扩展实现,每个实现都有自己的name, ...

  2. bzoj 3505 [Cqoi2014]数三角形 组合

    ans=所有的三点排列-共行的-共列的-斜着一条线的 斜着的枚举每个点和原点的gcd,反过来也可以,还能左右,上下挪 #include<cstdio> #include<cstrin ...

  3. openoffice转换pdf 异常问题查找处理 errorCode 525

    could not save output document; OOo errorCode: 525 该问题是由于java程序和openoffice的启动所属用户不同导致.使用以下命令查看端口和进程 ...

  4. Redis in .NET Core 入门:(4) LIST和SET

    第1篇:https://www.cnblogs.com/cgzl/p/10294175.html 第2篇 String:https://www.cnblogs.com/cgzl/p/10297565. ...

  5. C# 通俗说 委托(和事件)

    1.闲聊 编码一两年, 我走过了字段, 我跑过了类, 却翻不过方法.(不能灵活使用方法吧) (写这篇博客全程听将夜中<永夜>歌曲写完的,一气呵成,安利一下) 2.叙事 我们在编码中,经常捣 ...

  6. Error RZ3007: Targeted tag name cannot be null or whitespace

    Step 1: Disable precompile updating below property in csproj file: <MvcRazorCompileOnPublish>f ...

  7. 搭建基于Docker社区版的Kubernetes本地集群

    Kubernetes的本地集群搭建是一件颇费苦心的活,网上有各种参考资源,由于版本和容器的不断发展,搭建的方式也是各不相同,这里基于Docker CE的18.09.0版本,在Mac OS.Win10下 ...

  8. Dynamics CRM 配置 OAuth 2.0

    关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复124或者20140324可方便获取本文,同时可以在第一时间得到我发布的最新的博文信息,follow me! 本博文主要参考但不限于如下资料: ...

  9. ArcGIS API for JavaScript 4.x 本地部署之IIS法

    [导读] 关于如何在默认网站(Default Web Site,物理地址C:\inetpub\wwwroot\)启动,已有很多博客详尽地写好了. 本篇在自建网站(本机)中配置http而非https的j ...

  10. 华为模拟器eNSP安装(最新)网络工程师必备!

    电脑杂七杂八的东西太多了,于是今天把电脑重装系统了,正好重新安装一下华为模拟器eNSP,这个教程应该是最新的,因为eNSP版本更新以及华为官网页面的变化,有的小伙伴安装eNSP都下载不到安装包,接下来 ...