热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

撸个简朴的MVVM框架

理会Vue完成道理–怎样完成双向绑定mvvm本文能帮你做什么?1、相识vue的双向数据绑定道理以及中心代码模块2、减缓好奇心的同时相识怎样完成双向绑定几种完成双向绑定的做法如今几种

理会Vue完成道理 – 怎样完成双向绑定mvvm

《撸个简朴的MVVM框架》

本文能帮你做什么?

1、相识vue的双向数据绑定道理以及中心代码模块

2、减缓好奇心的同时相识怎样完成双向绑定

几种完成双向绑定的做法

如今几种主流的mvc(vm)框架都完成了单向数据绑定,而我所明白的双向数据绑定不过就是在单向绑定的基本上给可输入元素(input、textare等)增加了change(input)事宜,来动态修正model和 view,并没有多深邃。所以无需太甚介意是完成的单向或双向绑定。

完成数据绑定的做法有大抵以下几种:

宣布者-定阅者形式(backbone.js)

脏值搜检(angular.js)

数据挟制(vue.js)

宣布者-定阅者形式: 平常经由过程sub, pub的体式格局完成数据和视图的绑定监听,更新数据体式格局一般做法是 vm.set('property', value),不太熟悉去问一下度娘

这类体式格局如今毕竟太low了,我们更愿望经由过程 vm.property = value这类体式格局更新数据,同时自动更新视图,因而有了下面两种体式格局

脏值搜检: angular.js 是经由过程脏值检测的体式格局比对数据是不是有更改,来决议是不是更新视图,最简朴的体式格局就是经由过程 setInterval() 定时轮询检测数据更改,固然Google不会这么low,angular只要在指定的事宜触发时进入脏值检测,大抵以下:

  • DOM事宜,比如用户输入文本,点击按钮等。( ng-click )
  • XHR响应事宜 ( $http )
  • 浏览器Location更改事宜 ( $location )
  • Timer事宜( $timeout , $interval )
  • 实行 $digest() 或 $apply()

数据挟制: vue.js 则是采纳数据挟制连系宣布者-定阅者形式的体式格局,经由过程Object.defineProperty()来挟制各个属性的settergetter,在数据更改时宣布音讯给定阅者,触发响应的监听回调。

思绪整顿

已相识到vue是经由过程数据挟制的体式格局来做数据绑定的,其中最中心的要领就是经由过程Object.defineProperty()来完成对属性的挟制,到达监听数据更改的目标,无疑这个要领是本文中最主要、最基本的内容之一,假如不熟悉defineProperty,猛戳这里 整顿了一下,要完成mvvm的双向绑定,就必须要完成以下几点:

  1. 完成一个数据监听器Observer,可以对数据对象的一切属性举行监听,若有更改可拿到最新值并关照定阅者
  2. 完成一个指令剖析器Compile,对每一个元素节点的指令举行扫描和剖析,依据指令模板替代数据,以及绑定响应的更新函数
  3. 完成一个Watcher,作为衔接Observer和Compile的桥梁,可以定阅并收到每一个属性更改的关照,实行指令绑定的响应回调函数,从而更新视图
  4. mvvm进口函数,整合以上三者

《撸个简朴的MVVM框架》

不多赘述,一言不合就上图

人人可去下载去详细文件内里看,我写了详实的解释,每一个模块的功用,分工,每一个要领使命,等等

上图为小编我依据本身的明白后从新绘制,本盘算绘制再细一些,觉得会让人明白更庞杂然后就有了上图,代码中假如题目,迎接斧正,一同进修,你们的start是小编的动力

下面为详细代码完成,为了人人轻易我照样粘贴在readme内里,每一个文件不多说了,前面做了案牍及脑图思绪梳理,文件里我也了详实的解释

MVVM.html


















{{msg}}



MVVM.js

/**
* -----------------------------------------------------
* 1、完成数据代办
* 2、模版剖析
* 3、挟制监一切的属性
* -----------------------------------------------------
*/
class MVVM {
/**
*Creates an instance of MVVM.
* @param {*} options 当前实例通报过来的参数
* @memberof MVVM
*/
constructor(options){
this.$opt = options|| {}
this.$data = options.data;
// 完成数据代办
Object.keys(this.$data).forEach((key)=>{
this._proxyData(key)
})
// 挟制监一切的属性
observe(this.$data,this)
// 模版编译
new Compile(options.el || document.body,this)
}
_proxyData(key){
Object.defineProperty(this,key,{
configurable:false,
enumerable:true,
get(){
return this.$data[key]
},
set(newVal){
this.$data[key] = newVal
}
})
}
}

Observer.js

/**
* -----------------------------------------------------
* 1、完成一个数据监听器Observer
* 2、关照和增加定阅者
* -----------------------------------------------------
*/
class Observer {
/**
*Creates an instance of Observer.
* @param {*} data 须要挟制监听的数据
* @memberof Observer
*/
constructor(data){
this.$data = data || {}
this.init()
}
init(){
Object.keys(this.$data).forEach(key=>{
this.defineReative(key,this.$data[key])
})
}
defineReative(key,val){
// 建立宣布者-定阅者
let dep = new Dep()
// 再去视察子对象
observe(val)
Object.defineProperty(this.$data,key,{
configurable:false,
enumerable:true,
get(){
// 增加定阅者
Dep.target && dep.addSub(Dep.target)
return val
},
set(newVal){
if( newVal == val ) return false;
val = newVal
// 新的值是object的话,举行监听
observe(newVal)
// 关照定阅者
dep.notfiy()
}
})
}
}
/**
* 是不是举行挟制监听
*
* @param {*} value 监听对象
* @param {*} vm 当前实例
* @returns 返回 监听实例
*/
function observe(value, vm) {
if (!value || typeof value !== 'object') {
return;
}
return new Observer(value);
};
class Dep{
constructor(){
this.subs = []
}
/**
*保护定阅者数组
*
* @param {*} sub 定阅实例
* @memberof Dep
*/
addSub(sub){
this.subs.push(sub)
}
notfiy(){
this.subs.forEach(sub=>{
// 关照数据更新
sub.update()
})
}
}

Compile.js

/**
* -----------------------------------------------------
* 1、取实在dom节点
* 2、我们fragment 建立文档碎片,将真是dmo,挪动指缓存
* 3、编译假造dom,剖析模版语法
* 4、回填至真是dom,完成模版语法剖析,更新试图
* -----------------------------------------------------
*/
class Compile{
/**
*
*Creates an instance of Compile.
* @param {*} el dmo选择器
* @param {*} vm 当前实例
* @memberof Compile
*/
constructor(el,vm){
this.$vm = vm;
this.$el = this.isElementNode(el) ? el : document.querySelector(el)
if(this.$el){
this.$fragment = this.node2Fragment(this.$el)
this.init()
this.$el.appendChild(this.$fragment)
}
}
init(){
this.compileElement(this.$fragment)
}
/**
*
* 编译element
* @param {*} el dmo节点
* @memberof Compile
*/
compileElement(el){
// 1、取一切子节点
let childNodes = el.childNodes
// 2、轮回子节点
Array.from(childNodes).forEach((node)=>{
// 推断是文本节点照样dom节点
if(this.isElementNode(node)){
this.compileDom(node)
}else if (this.isTextNode(node)){
this.compileText(node)
}
// 推断当前节点是不是有子节点,假若有,递归查找
if(node.childNodes && node.childNodes.length){
this.compileElement(node)
}
})
}
/**
*
* 编译元素节点
* @param {*} node 须要编译的当前节点
* @memberof Compile
*/
compileDom(node){
// 取当前节点的属性鸠合
let attrs = node.attributes
// 轮回属性数组
Array.from(attrs).forEach(attr => {
let attrName = attr.name
// 推断当前属性是不是是指令
if(this.isDirective(attrName)){
let [,dir] = attrName.split("-")
let expr = attr.value
//推断当前属性是一般指令照样事宜指令
if(this.isEventDirective(dir)){
compileUtil.eventHandler(node,expr,dir,this.$vm)
}else{
compileUtil[dir] && compileUtil[dir](node,expr,this.$vm)
}
}
});
}
/**
*
* 编译文本节点
* @param {*} node 须要编译的当前节点
* @memberof Compile
*/
compileText(node){
var text = node.textContent;
var reg = /\{\{(.*)\}\}/;
if(reg.test(text)){
compileUtil.text(node,RegExp.$1,this.$vm)
}
}
/**
* 推断是不是是元素节点
*
* @param {*} el 节点
* @returns 是不是
* @memberof Compile
*/
isElementNode(el){
return el.nodeType == 1
}
/**
* 过滤是不是是指令
*
* @param {*} name 属性名
* @returns 是不是
* @memberof Compile
*/
isDirective(name){
return name.indexOf("v-") == 0
}
/**
* 推断是不是是事宜指令
*
* @param {*} dir 指令,on:click
* @returns 是不是
* @memberof Compile
*/
isEventDirective(dir){
return dir.indexOf("on") == 0
}
/**
* 推断是不是是文本节点
*
* @param {*} el 节点
* @returns 是不是
* @memberof Compile
*/
isTextNode(el){
return el.nodeType == 3
}
/**
* 将实在dom拷贝到内存中
*
* @param {*} el 实在dom
* @returns 文档碎片
* @memberof Compile
*/
node2Fragment(el){
let fragment = document.createDocumentFragment();
let children
while(children = el.firstChild){
fragment.appendChild(el.firstChild)
}
return fragment
}
}
// 指令处置惩罚东西
let compileUtil = {
/**
* 处置惩罚文本节点
*
* @param {*} node 当前节点
* @param {*} expr 表达式
* @param {*} vm 当前实例
*/
text(node,expr,vm){
this.buid(node,expr,vm,"text")
},
/**
* 处置惩罚表单元素节点
*
* @param {*} node 当前节点
* @param {*} expr 表达式
* @param {*} vm 当前实例
*/
model(node,expr,vm){
this.buid(node,expr,vm,"model")
var me = this,
val = this.getVMVal(vm, expr);
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
me.setVMVal(vm, expr, newValue);
val = newValue;
});
},
/**
* 事宜处置惩罚
*
* @param {*} node 当前节点
* @param {*} expr 表达式
* @param {*} dir 指令
* @param {*} vm 当前实例
*/
eventHandler(node,expr,dir,vm){
let [,eventType] = dir.split(":");
let fn = vm.$opt.methods && vm.$opt.methods[expr]
if(eventType && fn){
node.addEventListener(eventType,fn.bind(vm),false)
}
},
/**
* 绑定事宜一致处置惩罚要领抽离,增加watcher
*
* @param {*} node 当前节点
* @param {*} expr 表达式
* @param {*} vm 当前实例
* @param {*} dir 指令
*/
buid(node,expr,vm,dir){
let updateFn = update[dir+'Update']
updateFn && updateFn(node,this.getVMVal(vm,expr))
new Watcher(vm, expr, function(value, oldValue) {
updateFn && updateFn(node, value, oldValue);
});
},
/**
* 猎取表达式代表的值
*
* @param {*} vm 当前实例
* @param {*} expr 表达式
* @returns
*/
getVMVal(vm,expr){
// return vm[expr] 要斟酌,a.b.c的状况
let exp = expr.split(".");
let val = vm
exp.forEach((k)=>{
val = val[k]
})
return val
},
/**
* 设置更新数据里对应的表达式的值
*
* @param {*} vm
* @param {*} expr
* @param {*} newValue
*/
setVMVal(vm,expr,newValue){
let exp = expr.split('.');
let val = vm
exp.forEach((key,i)=>{
if(i val = val[key]
}else{
val[key] = newValue
}
})
}
}
// 数据更新
let update = {
textUpdate(node,value){
node.textCOntent= typeof value == 'undefined' ? '' : value
},
modelUpdate(node,value){
node.value = typeof value == 'undefined' ? '' : value
}
}

Watcher.js

/**
* -----------------------------------------------------
* 1、完成一个Watcher,作为衔接Observer和Compile的桥梁
* 2、关照和增加定阅者
* -----------------------------------------------------
*/
class Watcher {
/**
*Creates an instance of Watcher.
* @param {*} vm 当前实例
* @param {*} expOrFn 表达式
* @param {*} cb 更新回挪用
* @memberof Watcher
*/
constructor(vm,expOrFn,cb){
this.$vm = vm
this.$expOrFn = expOrFn
this.$cb = cb
this.value = this.get()
}
get(){
// 增加定阅者
Dep.target = this;
// let dep = new Dep()
// 去modal中取值,这个时刻必然会触发defineProperty的getter,真正的push定阅者
let value = this.getVMVal(this.$vm,this.$expOrFn)
// 用完了,重置归去
Dep.target = null
return value
}
/**
* 取modal里的值
*
* @param {*} vm 当前实例
* @param {*} expr 表达式
* @returns 返回指
* @memberof Watcher
*/
getVMVal(vm,expr){
// return vm[expr] 要斟酌,a.b.c的状况
let exp = expr.split(".");
let val = vm
exp.forEach((k)=>{
val = val[k]
})
return val
}
// 对外暴露的跟新要领,比较新老值,获得定阅关照举行更新
update(){
let oldVal = this.value;
let newVal = this.getVMVal(this.$vm,this.$expOrFn)
if (newVal !== oldVal) {
this.value = newVal;
this.$cb(newVal, oldVal);
}
}
}

末了感谢您的浏览


推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
author-avatar
左伊
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有