接上文:一套代码小程序&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. MySQL 开发实践 8 问,你能 hold 住几个?

    最近研发的项目对DB依赖比较重,梳理了这段时间使用MySQL遇到的8个比较具有代表性的问题,答案也比较偏自己的开发实践,没有DBA专业和深入,有出入的请使劲拍砖!- MySQL读写性能是多少,有哪些性 ...

  2. orcl数据库命令行怎么导入dmp格式的文件

    2018-05-23 1.创建空间 以system的身份登陆orcl 打开SQL Window界面,输入以下命令create tablespace SGXC(表空间的名字)datafile 'D:/S ...

  3. 华盛顿邮报:FBI 屡次夸大了“手机加密威胁”的数字

    <华盛顿邮报>周二报道称,美国联邦调查局(FBI)严重夸大了由加密手机所造成的问题.以去年为例,该机构调查人员声称被大约 7800 部涉嫌犯罪活动的加密设备挡在了门外,而准确的数字应该在 ...

  4. MIP 移动网页加速器视频教程全新发布

    MIP (Mobile Instant Pages - 移动网页加速器) 是百度推出的开源项目,用于移动端页面加速.MIP 技术通过优化浏览器资源加载,前端代码执行及 CDN 缓存加速来加速页面,打造 ...

  5. Java注解(二):实战 - 直接使用对象列表生成报表

    通过对Java注解(一):介绍,思想及优点学习了解,相信大家对Java注解有一定程度的了解,本篇文章将实战项目中的应用来加深对Java注解的了解. 本实例实现根据指定字段的JavaBean,生成对应列 ...

  6. RK3399配置笔记

    1. adb shell 默认超级管理员 在build/core/main.mk下将ADDITIONAL_DEFAULT_PROPERTIES += ro.secure=1改成ADDITIONAL_D ...

  7. css属性分类介绍

    css属性分类介绍 CSS分类目录 文本/字体/颜色 文本相关 字体相关 颜色相关 背景相关 大小/布局 大小属性 margin 外边距 padding 内边距 border 边框 position ...

  8. 机器学习——KMeans聚类,KMeans原理,参数详解

    0.聚类 聚类就是对大量的未知标注的数据集,按数据的内在相似性将数据集划分为多个类别,使类别内的数据相似度较大而类别间的数据相似度较小,聚类属于无监督的学习方法. 1.内在相似性的度量 聚类是根据数据 ...

  9. Mybaits-plus实战(一)

    1. Mybaits-plus实战(一) 1.1. 快速开始 1.1.1. 配置文件 # 扫描位置 mybatis-plus.mapper-locations=classpath:/mapper/*M ...

  10. Android6.0 源码修改之Settings音量调节界面增加通话音量调节

    前言 今天客户提了个需求,因为我们的设备在正常情况下无法调节通话音量,只有在打电话过程中,按物理音量加减键才能出现调节通话音量seekBar,很不方便,于是乎需求就来了.需要优化两个地方 1.在正常情 ...