热门标签 | 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);
}
}
}

末了感谢您的浏览


推荐阅读
  • 微软推出Windows Terminal Preview v0.10
    微软近期发布了Windows Terminal Preview v0.10,用户可以在微软商店或GitHub上获取这一更新。该版本在2月份发布的v0.9基础上,新增了鼠标输入和复制Pane等功能。 ... [详细]
  • Framework7:构建跨平台移动应用的高效框架
    Framework7 是一个开源免费的框架,适用于开发混合移动应用(原生与HTML混合)或iOS&Android风格的Web应用。此外,它还可以作为原型开发工具,帮助开发者快速创建应用原型。 ... [详细]
  • 第二十五天接口、多态
    1.java是面向对象的语言。设计模式:接口接口类是从java里衍生出来的,不是python原生支持的主要用于继承里多继承抽象类是python原生支持的主要用于继承里的单继承但是接 ... [详细]
  • 大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式
    大类|电阻器_使用Requests、Etree、BeautifulSoup、Pandas和Path库进行数据抓取与处理 | 将指定区域内容保存为HTML和Excel格式 ... [详细]
  • 本文介绍了如何使用 Node.js 和 Express(4.x 及以上版本)构建高效的文件上传功能。通过引入 `multer` 中间件,可以轻松实现文件上传。首先,需要通过 `npm install multer` 安装该中间件。接着,在 Express 应用中配置 `multer`,以处理多部分表单数据。本文详细讲解了 `multer` 的基本用法和高级配置,帮助开发者快速搭建稳定可靠的文件上传服务。 ... [详细]
  • 在PHP中如何正确调用JavaScript变量及定义PHP变量的方法详解 ... [详细]
  • 本指南详细介绍了如何利用华为云对象存储服务构建视频点播(VoD)平台。通过结合开源技术如Ceph、WordPress、PHP和Nginx,用户可以高效地实现数据存储、内容管理和网站搭建。主要内容涵盖华为云对象存储系统的配置步骤、性能优化及安全设置,为开发者提供全面的技术支持。 ... [详细]
  • DVWA学习笔记系列:深入理解CSRF攻击机制
    DVWA学习笔记系列:深入理解CSRF攻击机制 ... [详细]
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • Python 数据可视化实战指南
    本文详细介绍如何使用 Python 进行数据可视化,涵盖从环境搭建到具体实例的全过程。 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • XAMPP 遇到 404 错误:无法找到请求的对象
    在使用 XAMPP 时遇到 404 错误,表示请求的对象未找到。通过详细分析发现,该问题可能由以下原因引起:1. `httpd-vhosts.conf` 文件中的配置路径错误;2. `public` 目录下缺少 `.htaccess` 文件。建议检查并修正这些配置,以确保服务器能够正确识别和访问所需的文件路径。 ... [详细]
  • 装饰者模式(Decorator):一种灵活的对象结构设计模式
    装饰者模式(Decorator)是一种灵活的对象结构设计模式,旨在为单个对象动态地添加功能,而无需修改原有类的结构。通过封装对象并提供额外的行为,装饰者模式比传统的继承方式更加灵活和可扩展。例如,可以在运行时为特定对象添加边框或滚动条等特性,而不会影响其他对象。这种模式特别适用于需要在不同情况下动态组合功能的场景。 ... [详细]
  • 您的数据库配置是否安全?DBSAT工具助您一臂之力!
    本文探讨了Oracle提供的免费工具DBSAT,该工具能够有效协助用户检测和优化数据库配置的安全性。通过全面的分析和报告,DBSAT帮助用户识别潜在的安全漏洞,并提供针对性的改进建议,确保数据库系统的稳定性和安全性。 ... [详细]
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社区 版权所有