实现一个简单的vue响应式数据的功能

一、功能需求

自己写一个vue程序定义为Myvue,功能是实现简单的数据双向绑定

二、效果预览

在这里插入图片描述
在这里插入图片描述

三、代码实现与解析

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style>
			#app{
				text-align: center;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<input type="text" v-model="number" />
			<button type="button" v-click="btnhandle">增加计数</button>
			<p v-bind="number" style="font-size: 22px;font-weight: 600;"></p>
		</div>
		<script type="text/javascript">
			// 实现流程总结:
			// 1.定义Myvue构造函数,构造函数主要功能是执行初始化,
			// 2.new一个Myvue实例,相当于vue实例,包含data,methods等等
			// 3.原型中定义初始化函数,主要功能是保存属性和定义方法
			// 4.定义_obverse函数重写data中的set与get函数,让他变成响应式数据
			// 5.定义_complie函数,监听三个指令的操作(如v-click的dom中的点击事件绑定到methods定义的事件中,
			// v-model监听输入框的值赋值到data中,使data的值改变,从而触发data的set函数,通过update函数更新dom数据)
			// 定义构造函数Myvue
			function Myvue(options) {
				// option:Myvue实例的所有参数
				this._init(options)
			}
			// 重写data的set和get函数,并且更新值
			Myvue.prototype._obverse = function(obj) {
				var value
				// 遍历对象(监听对象,data中的参数)
				for (key in obj) {
					// 判断key是否自己的私有属性(非继承来的)
					if (obj.hasOwnProperty(key)) {
						value = obj[key]
						console.log('value', value)
						console.log('key', value)
						// 定义一个_directives数组保存映射关系
						this._binding[key] = {
							_directives: []
						}
						// 如果是对象递归遍历
						if (typeof value === 'object') {
							this._obverse(value)
						}
						var binding = this._binding[key]
						Object.defineProperty(this.$data, key, {
							enumerable: true,
							configurable: true,
							// 获取值
							get: function() {
								return value
							},
							set: function(newVal) {
								// 如果新值和旧值不一样,则需要更新
								if (value !== newVal) {
									// 更新data中的值
									value = newVal
									// 遍历映射关系,并且更新
									binding._directives.forEach(function(item) {
										// 遍历三个指令的Watcher实例,并且执行取得Myvue中的data中的参数赋值给dom中的value或者innerHTML的操作
										// 把data中值更新到dom
										item.update()
									})
								}
							}
						})
					}
				}
			}
			//添加指令函数,主要目的对每个dom节点的指令的属性值和Myvue中定义的属性进行对应
			// 如:v-click指令,找到该dom节点绑定点击事件,并且是methods中的定义的点击事件
			// v-model指令:监听输入框的输入值,赋值到Myvue的data中,创建Watcher实例并保存
			// v-bind指令:创建Watcher实例并保存
			Myvue.prototype._complie = function(root) {
				var _this = this
				// root:id为app的dom节点
				console.log('节点', root)
				// root.children:获取节点,[div,button,p]	
				var nodes = root.children
				for (var i = 0; i < nodes.length; i++) {
					var node = nodes[i]
					// 有子节点再遍历
					if (node.children.length) {
						this._complie(node)
					}
					// 判断v-click指令,给包含v-click指令的该节点绑定点击事件,从而实现点击时会执行methods中定义的点击事件
					if (node.hasAttribute('v-click')) {
						node.onclick = (function() {
							// 点击事件是一个methods中和dom节点中key值一致(事件名称一致)的函数
							var attrVal = nodes[i].getAttribute(('v-click'))
							// bind是使methods的作用域和data中的作用域保持一致
							return _this.$methods[attrVal].bind(_this.$data)
						})()
					}
					// 判断v-model指令,而且该指令是写在input和textarea标签中的,从而实现输入宽的值映射到data中,并且保存一个Watcher实例
					if (node.hasAttribute('v-model')&&(node.tagName==='INPUT' || node.tagName === 'TEXTAREA')) {
						// 给该节点绑定一个事件监听输入框输入事件
						node.addEventListener('input',(function(key) {
							// number
						    var attrVal = node.getAttribute('v-model')
							console.log('vmodel', attrVal)
							// 每个指令(v-bind,v-model,v-click)有自己的_directives数组
							// 传入一个Watcher实例,实例中保存了以下参数
							_this._binding[attrVal]._directives.push(new Watcher(
							   'input',
							   node,
							   _this,
							   attrVal,
							   'value'
							))
							return function() {
								// 监听输入框输入事件,把输入值映射到data中对应的参数中
								_this.$data[attrVal] = nodes[key].value
							}
						})(i))
					}
					// 判断v-bind,保存一个Watcher实例
					if (node.hasAttribute('v-bind')) {
						// number
						var attrVal = node.getAttribute('v-bind')
						_this._binding[attrVal]._directives.push(new Watcher(
						  'text',
						  node,
						  _this,
						  attrVal,
						  'innerHTML'
						))
					}
				}
			}
			// 构造函数原型挂载初始化方法
			Myvue.prototype._init = function(options) {
				console.log('初始化参数', options)
				console.log('作用域Myvue', this)
				this.$options = options
				// app的dom节点
				this.$el = document.querySelector(options.el)
				this.$data = options.data
				this.$methods = options.methods
				//_binding保存着model与view的映射关系,也就是我们前面定义的Watcher的实例。
				// 当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
				this._binding = {}
				this._obverse(this.$data)
				this._complie(this.$el)
			}
			// 定义watcher类,绑定更新函数,实现对dom的更新
			function Watcher(name, el, vm, exp, attr) {
				// 节点名称(input,)
				this.name = name
				// 指令对应的dom元素
				this.el = el
				// 指令所属myvue实例
				this.vm = vm
				// 指令对应的值,如本列的v-model = number中的number
				this.exp = exp
				// 绑定的属性值,取到标签的值,p标签按innerHTML取值,input则按value取值
				this.attr = attr
			}
			// 通过监听器把data中的参数值更新到dom中的函数
			Watcher.prototype.update = function() {
				// 取得Myvue中的data中的参数赋值给dom中的value或者innerHTML
				this.el[this.attr] = this.vm.$data[this.exp]
			}
			// 实例化myvue
			window.onload = function() {
				var app = new Myvue({
					el: '#app',
					data: {
						number: 0
					},
					methods: {
						btnhandle: function() {
							this.number++
							console.log('按钮', this.number)
						}
					}
				})
			}
		</script>
	</body>
</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值