一套代码小程序&Web&Native运行的探索04——数据更新
对应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——数据更新的更多相关文章
- 一套代码小程序&Web&Native运行的探索05——snabbdom
接上文:一套代码小程序&Web&Native运行的探索04——数据更新 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/ma ...
- 一套代码小程序&Web&Native运行的探索06——组件系统
接上文:一套代码小程序&Web&Native运行的探索05——snabbdom 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tre ...
- 一套代码小程序&Web&Native运行的探索03——处理模板及属性
接上文:一套代码小程序&Web&Native运行的探索02 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/m ...
- 一套代码小程序&Web&Native运行的探索02
接上文:一套代码小程序&Web&Native运行的探索01,本文都是一些探索性为目的的研究学习,在最终版输出前,内中的内容可能会有点乱 参考: https://github.com/f ...
- 一套代码小程序&Web&Native运行的探索07——mpvue简单调研
前言 接上文:[一套代码小程序&Native&Web阶段总结篇]可以这样阅读Vue源码 最近工作比较忙,加之上个月生了小孩,小情人是各种折腾他爸妈,我们可以使用的独立时间片不多,虽然这 ...
- 一套代码小程序&Web&Native运行的探索01
前言 前面我们对微信小程序进行了研究:[微信小程序项目实践总结]30分钟从陌生到熟悉 并且用小程序翻写了之前一个demo:[组件化开发]前端进阶篇之如何编写可维护可升级的代码 之前一直在跟业务方打交道 ...
- 【一套代码小程序&Native&Web阶段总结篇】可以这样阅读Vue源码
前言 前面我们对微信小程序进行了研究:[微信小程序项目实践总结]30分钟从陌生到熟悉 在实际代码过程中我们发现,我们可能又要做H5站又要做小程序同时还要做个APP,这里会造成很大的资源浪费,如果设定一 ...
- 小程序 web 端实时运行工具
微信小程序 web 端实时运行工具 https://chemzqm.github.io/wept/
- 小程序web开发框架-weweb介绍
weweb是一个兼容小程序语法的前端框架,你可以用小程序的写法,来写web单面应用.如果你已经有小程序了,通过它你可以将你的小程序运行在浏览器中.在小程序大行其道的今天,它可以让你的小程序代码得到最大 ...
随机推荐
- ES6知识整理(1)--let和const命令
最近准备在业余空闲时间里一边学习ES6,一边整理相关知识.只有整理过的学习才是有效的学习.也就是学习之后要使用和整理成文,才是正在的学到了... 那么现在开始 LINK START!(首先是第一讲,前 ...
- 权限系统与RBAC模型概述[绝对经典]
0. 前言 一年前,我负责的一个项目中需要权限管理.当时凭着自己的逻辑设计出了一套权限管理模型,基本原理与RBAC非常相似,只是过于简陋.当时google了一些权限管理的资料,从中了解到早就有了RBA ...
- WebService-----Xfire
Xfire相对Axis2 个人感觉相对简单一点,因为myEclipse上可以自动生成.而且在目前来讲,Xfire也是更受欢迎一点. 首先说明Xfire所用jar包:http://download.cs ...
- ASP.Net Core MVC 发生二次请求
Bug回忆录 昨天搭建新框架的时候,遇到一个很奇怪的“Bug”,每次请求都会触发两次Aciton,举例子吧,Demo: _Layout.cshtml <!DOCTYPE html> < ...
- 性能测试入门 — LoadRunner 使用初探
前言: 性能测试是利用产品.人员和流程来降低应用程序.升级程序或补丁程序部署风险的一种手段.性能测试的主要思想是通过模拟产生真实业务的压力对被测系统进行加压,验证被测系统在不同压力情况下的表现,找出其 ...
- this指向和apply,call,bind三者的区别
一.前言 this指向,apply,call,bind的区别是一个经典的面试问题,同时在项目中会经常使用到的原生的js方法.同时也是ES5中的众多坑的一个.ES6中可能会极大的避免了this产生的错误 ...
- Redis介绍使用及进阶
目录: 一.介绍 二.缓存问题 三.Redis内存滥用 四.键命名规范 五.Redis使用场景 六.持久化操作 七..Net Core 使用redis 简单介绍 一.介绍 1. 高性能-- Redis ...
- 结合JDK源码看设计模式——装饰者模式
定义 在不改变原有对象的基础之上,将功能附加到对象上 适用场景 扩展一个类的功能 动态的给对象增加功能,当功能不需要的时候能够动态删除 详解 在看到定义的时候,可能很多人会想,这不就是继承吗?的确很像 ...
- jQuery --- 第四期 (jQuery动效)
学习笔记 1.jQuery动画的淡入淡出 <!doctype html> <html> <head> <meta charset="utf-8&qu ...
- Odoo11入门请假单模块学习教程源码
欢迎转载,但必须明显标注转载地址. 系统:Windows10 版本:Odoo11 一.创建模块 1.创建一个模块的骨架结构目录(在windows 的 CMD中) E:\Odoo 11.0> py ...