原生javascript 100行js代码实现一个mvvm框架

news/2024/7/10 1:57:32 标签: vue, mvvm, js, 双向数据绑定, 前端框架

一,基础知识

1,何为MVVM(双向数据绑定

双向数据绑定(MVVM):数据(M)发生变化时立即影响视图(V),而视图(V)发生改变也会立即影响数据(M)

2,实现数据绑定的方法

实现数据绑定的做法有大致如下几种:

1,观察者模式(backbone)

发布者发布事件,观察者监听事件。当某些方法被触发时,就通知观察者执行预定操作。
观察者可以使用自己写也可以使用es7最新添加的数据绑定方法Object.observe()。资料参考
https://www.w3ctech.com/topic/1097

2,脏值检查(angular)

angular就是通过脏值检测实现的。首先angular会解析dom中的命令,然后记录所有变量的当前值,当发生触发操作之后,通过apply或者digest进入脏检查环节。上次记录值与当前值是否一致。不一致就更新视图,然后再脏值检测一次直到数据不再发生变化。如果一致就不做任何操作

vue_12">3,设置属性访问器(vue

通过Object.defineProperty()来获取每个属性的setter,getter,在数据变动时通知订阅者进行处理。

二,动手实现一个MVVM框架

1,目标与使用规则

我们想要实现的效果是这样的:

这里写代码片

使用类vue的定义方式,即通过自定义元素属性来进行数据标记。el绑定html的作用域,data绑定数据,@+事件名绑定方法。
任何一个绑定了数据"data"(data=“say”)的输入框的内容发生变动,都将即时更新所有绑定了"data"数据的网页元素。点击绑定了事件(@click=“go()”)的元素,会触发与点击事件绑定的方法(“go()”) 。

<!--html部分:-->
<div id="mvEl">
	<p id="label" data="say">这个p标签绑定了数据"say",这里绑定的是innerText</p>
	<input id="input" type="text" value='这个输入框的值也绑定了数据"say"' data="say" />
	<div @click="go" data="say">这个div标签绑定了点击事件"go"。
		<input id="input2" type="text" value="这是一个嵌套在div标签中的输入框,它也绑定了数据say,这里绑定的是value" data="say" />
	</div>
</div>
js"><!--js部分:-->
<script type="text/javascript">
//实例化mvvm
var vm = new mvvm({
	//绑定域
	el: "mvEl",
	//绑定数据
	data: {
		say: "这是数据1",
		say1: "这是数据2"
	},
	//动作方法
	action: {
		go: () => {
			vm.data.say+="废话";
			console.info("这是个方法呀");
		}
	}
});
<script>
2,发布订阅

为每个绑定的元素设置访问器,当setter触发时就通知所有的订阅者,以此达到即时更新的效果。

js">			
			//发布器   
			function observe(data, dep) {
				if (!data || typeof data !== 'object') {
					return;
				}
				// 获取data中的所有属性(say,say1)
				Object.keys(data).forEach((key) => {
					defineReactive(data, key, data[key], dep);
				});
			};
			//为该对象设置属性,添加getter,setter,订阅者通知
			function defineReactive(data, key, val, dep) {
				// 监听子属性
				observe(val);
				//设置访问器及属性
				Object.defineProperty(data, key, {
					// 可枚举
					enumerable: true,
					// 不能再define
					configurable: false,
					//设置getter
					get: () => {
						return val;
					},
					//设置setter
					set: (newVal) => {
						//console.log(val, '=>', newVal);
						//更新值
						val = newVal;
						// 通知所有订阅者
						dep.notify();
					}
				});
			}
			//订阅器
			function dep() {
				//订阅数组
				this.subs = [];
				//添加订阅
				this.addSub = (sub) => {
					this.subs.push(sub);
				};
				//删除订阅
				this.delSub = (key) => {
					// 	//delete sub
				};
				//触发回调,通知所有订阅者,触发update()
				this.notify = () => {
					this.subs.forEach((sub) => {
						sub.update();
					});
				}
			};
			
			let dep = new Dep();
			observe(vm.data, dep);
3,html指令解析与属性处理

在html部分中,我们在元素标签上使用的自定义属性“data”与“@click”,下面的代码将演示如何处理自定义属性

要想读取每个元素的属性,首先必须获取所有网页元素,然后再遍历每个元素的属性。而dom对象的children属性会方便地告知你当前元素下有多少子元素。

js">//循环获取所有dom树
function mvvm(vm) {
	//绑定域 获取主元素
	let el = document.getElementById(vm.el);
	//循环所有网页元素
	function eachElDo(el, vm) {
		//处理data属性
		attrHandler_data(el, vm);
		//处理事件方法
		attrHandler_event(el, vm)
		for (var i = 0; i < el.children.length; i++) {
			eachElDo(el.children[i], vm);
		}
	}
	eachElDo(el, vm)
}

遍历该元素下的所有属性,处理自定义属性。值得注意的是不同网页标签取值方式不同。比如p的内容是在标签内的,而获取input内容则需要使用value,由于div是布局元素,所以即使里面绑定了data也不会被显示出来。判定标签使用的是.localName。不是应该使用tagName么?localname默认都是小写,我也就直接拿来用了,,,

js">//处理data属性,并绑定数据
function attrHandler_data(el, vm) {
	if (el.getAttribute("data")) {
		//回调方法,设定发生数据更新时的动作
		let action = {
			update: () => {	
				//设定不同网页元素标签,使用不同的取值方式。		
				switch (el.localName) {
					case "input":
						el.value = vm.data[el.getAttribute("data")];
						break;
					case "div":
						break;
					default:
						el.innerText = vm.data[el.getAttribute("data")];
				}
			}
		}
		//输入(事件)绑定  为绑定了data的元素添加事件。
		switch (el.localName) {
			case "input":
				el.addEventListener('input', () => {
					vm.data[el.getAttribute("data")] = el.value;
				})
				break;
			case "div":
				break;
			default:
				el.addEventListener('onchange', () => {
					vm.data[el.getAttribute("data")] = el.innerText;
				})
		}
		//添加观察者
		dep.addSub(action);
	}
}

解析事件指令
这里需要注意的是我们的指令是“@”+事件名。事件名就是html默认的时间click、onchange、touchover等等。写法是<input @click=“go” /> 。我们需要先把@与click分离,再绑定到事件方法adEventListener上。

js">			function attrHandler_event(el, vm) {
				//遍历元素所有属性
				for (var i = 0; i < el.attributes.length; i++) {
					//是否有事件属性
					if (/@/i.test(el.attributes[i].nodeName)) {
						//用正则分离"@"与事件名
						let myEvent = el.attributes[i].nodeName.replace("@", "");
						//绑定事件
						el.addEventListener(myEvent, () => {
							//事件回调方法
							vm.action[el.getAttribute('@' + myEvent)]();
						}, false)
					}
				}
			}

到此为止,100行就实现了类vuemvvm框架。
准备继续更新一些新的功能,有兴趣的同学可以 git:https://github.com/155366311/mvvm


http://www.niftyadmin.cn/n/1338421.html

相关文章

总投资近120亿元 126个智慧城市项目闪耀青岛

青岛是国家智慧城市技术和标准双试点城市&#xff0c;6个区市或区域成为国家智慧城市试点。记者从市经信委了解到&#xff0c;青岛智慧城市的建设组织实施两年来&#xff0c;共推进126个重点项目&#xff0c;总投资118.8亿元。截至目前&#xff0c;已有60个项目建成&#xff0c…

unity 七种坐标系统详解与互相转换的方法 模型坐标、世界坐标、观察坐标(视口坐标)、裁剪坐标、屏幕坐标、ui坐标、uv坐标

简述&#xff1a;本文会详细介绍三维软件中7种坐标的特点与关系&#xff0c;还有在脚本编辑和shader系统中的使用方法后续更新 不同空间中坐标的转换方法 七种坐标系简介 unity 使用的是左手坐标系&#xff0c;即&#xff1a; ↑Y&#xff0c;↓-Y&#xff0c;前Z&#xff0c…

三季度Avery Dennison的RFID产品销售额提升35%

三季报投资者和分析师的电话会议上&#xff0c;Avery Dennison CEO Mitch Butier称公司零售品牌及信息解决方案(RBIS)领域销售额增长了2%。Butier在电话会议表示&#xff0c;销售额增长主要是由RFID产品驱动&#xff0c;今年三季度RFID产品销售额提升了35%&#xff0c;预计整年…

blender2.8 使用教程 贴图纹理快捷键等。

新版本blender2.8虽然与2.79版本号只差0.01&#xff0c;但却有着天翻地覆的变化。新版本功能更加强大&#xff0c;交互更加人性&#xff0c;但也有不少问题。 新版本不仅支持雕塑功能&#xff0c;还支持编辑uv&#xff0c;但是&#xff0c;mac系统瞎绝不能用 texturepaint 闪退…

《计算机网络:自顶向下方法(原书第6版)》一课后习题和问题

本节书摘来华章计算机《计算机网络&#xff1a;自顶向下方法&#xff08;原书第6版&#xff09;》一书中的第2章 &#xff0c;&#xff08;美&#xff09;James F.Kurose Keith W.Ross 著 陈 鸣 译 更多章节内容可以访问云栖社区“华章计算机”公众号查看。 课后习题和问题 …

unity Shader Lab(cg hlsl glsl)着色器入门教程 以及 vs2019 支持unity shader语法(更新中2019.9.5)

前言&#xff1a; 如果你对cg glsl hlsl 顶点着色器 片段着色器 表面着色器 固定渲染管线 等等有所疑惑&#xff0c;或是想学会unity的渲染&#xff0c;看这一篇就足够了。另外我博客的shader分类中还有很多shader教程和源码&#xff0c;每篇源码都有实现思路、语法功能注释&a…

tensorflow 2 keras gan手写数字 对抗学习注解

该段实例来源于网络&#xff0c;做了一些修改和标注 from __future__ import absolute_import, division, print_function, unicode_literals import tensorflow as tf from tensorflow import keras import glob import imageio import matplotlib.pyplot as plt import nump…

《简明电路分析》——1.7节本章小结1

本节书摘来自华章社区《简明电路分析》一书中的第1章&#xff0c;第1.7节本章小结1&#xff0c;作者钟洪声 吴 涛 孙利佳&#xff0c;更多章节内容可以访问云栖社区“华章社区”公众号查看 1.7 本章小结1电路分析的对象是电路模型&#xff0c;电路模型是从实际电路抽象出来…