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

一,基础知识

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进入脏检查环节。上次记录值与当前值是否一致。不一致就更新视图,然后再脏值检测一次直到数据不再发生变化。如果一致就不做任何操作

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部分:-->
<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触发时就通知所有的订阅者,以此达到即时更新的效果。

			
			//发布器   
			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属性会方便地告知你当前元素下有多少子元素。

//循环获取所有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默认都是小写,我也就直接拿来用了,,,

//处理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上。

			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行就实现了类vue的mvvm框架。
准备继续更新一些新的功能,有兴趣的同学可以 git:https://github.com/155366311/mvvm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千年奇葩

从来没受过打赏,这玩意好吃吗?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值