Vue双向数据绑定之原理及实现4

一、Compiler解析器

解析器的作用一方面是解析出视图中相关的指令,将数据填充到视图中,另一方面也是添加新的订阅者,在数据发生更新时,能同步更新到视图中。
有了解析器以后,整个模型就算是完整了,参考下图:
在这里插入图片描述

Vue中的模板指令非常多,而且也做了很多的兼容,所以我们这只做例如v-model, {{}}, v-on
基本思路:

1、把真实DOM元素转换为文档片段;
2、遍历文档片段中所有的节点,解析出双括号指令和v-xxx的指令;
3、根据不同的指令,添加不同的操作:
    v-model: 初始化数据,添加订阅器,同时添加input事件;
    v-text: 初始化数据,添加订阅器;
    v-on: 为当前元素节点添加对应的事件和回调;

定义Compiler解析器类:

function Compiler(vm, el){
    this.vm = vm		// Vue对象
    this.el = document.querySelector(el)		// 挂载点
    this.fragment = null		// 文档片段
    this.init()	// 初始化
}

初始化操作:

Compiler.prototype = {
    init(){
        if (this.el) {
            this.fragment = this.nodeToFragment(this.el) // 文档片段
            this.compileElement(this.fragment)  // 解析
            this.el.appendChild(this.fragment)  // 将文档片段添加到视图中
        } else {
            console.log('Dom元素不存在');
        }
    },
}

文档片段处理:

Compiler.prototype = {
    ...
    nodeToFragment(el){	// 文档片段处理
	    var fragment = document.createDocumentFragment()
	    var child = el.firstChild
	    while (child) {
	        fragment.appendChild(child)
	        child = el.firstChild
	    }
	    return fragment
    },
}

解析操作,对应v-modelv-on{{}}是不有不同操作的,所以要进行判断处理。
v-textv-on是元素属性中的,而{{}}是在元素中的`innerHTML。

Compiler.prototype = {
    ...
    compileElement(fragment){	// 解析元素节点
	    var childNodes = fragment.childNodes;
	
	    [].slice.call(childNodes).forEach(node=>{
	    	var reg = /\{\{(.*)\}\}/
        	var text = node.textContent
	        
	        if(this.isElementNode(node))	{	// 元素节点
	        	this.compileAttr(node)
	        } else if (this.isTextNode(node) && reg.test(text)) {	// 文本节点
	            this.compileText(node, reg.exec(text)[1])
	        }
	
	        // 继续递归遍历当前节点的子节点
	        if (node.childNodes && node.childNodes.length) {
	            this.compileElement(node);
	        }
	    });
    },
    isElementNode(node){	// 元素节点
    	return node.nodeType === 1
    },
    isTextNode(node){	// 文本节点
    	return node.nodeType === 3
    },
}

先处理{{}},只需要先将数据更新到界面中,并创建新的订阅者(会添加到订阅器中):

Compiler.prototype = {
    ...
    compileText(node, exp){	// {{}}的处理
    	var val = this.vm[exp]	// 获取属性值
    	node.textContent = val	// 更新到页面中
    	new Watcher(this.vm, exp, value=>{ // 新的订阅者(绑定更新函数即可)
            node.textContent = value
    	})
    },
}

对于元素中的属性v-modelv-on绑定处理的,又需要判断出是那种,v-on这只需要绑定上对应的事件即可;而v-model会不一样,所有需要区分处理:

Compiler.prototype = {
    ...
    compileAttr(node){	// v-xxx的处理
    		// 获取当前节点上所有的属性节点
    		var nodeAttrs = node.attributes;
    		var self = this;
    		
    		// 遍历属性,找到是否有v-xxx
    		[].slice.call(nodeAttrs).forEach(attr=>{
    			var attr_name = attr.name
    			
    			var reg = /v\-/;
    			if(reg.test(attr_name)){		// v-xxx指令
    				var exp = attr.value		// 对应的事件
    				let dir = attr_name.substring(2)		// 字符串切分 v-   xxx
    				
    				reg = /on\:/;
    				if(reg.test(dir)){	// v-on事件绑定
    					self.compileEvent(node, self.vm, exp, dir)
    				} else{	// v-model双向数据绑定	[备注: 我们只做几个指令操作]
    					self.compileModel(node, self.vm, exp, dir)
    				}
    				
    				
    				// 操作完成后,删除对应的属性
    				node.removeAttribute(attr_name)
    			}
    		})
    },
}

v-on事件绑定的处理,根据属性值,获取事件句柄,绑定上即可:

Compiler.prototype = {
    ...
    compileEvent(node, vm, exp, dir){	// v-on的处理
    		// 例如   v-on:click='xxxx'
    		// 获取事件类型
    		var eventType = dir.split(':')[1]
    		// 根据事件名,获取函数句柄
    		var callback = vm.methods && vm.methods[exp]
    		if(eventType && callback){	// 有事件名且定义有对应事件处理
    			node.addEventListener(eventType, callback.bind(vm), false)
    		} else {
    			console.log('请在methods中添加对应的方法: ' + exp)
    		}
    },
}

v-model双向数据绑定,数据先要更新到界面中,接着是创建新的订阅者(会添加到订阅器中),最后还要对输入事件的处理:

Compiler.prototype = {
    ...
    compileModel(node, vm, exp, dir){	// v-model的处理,为model绑定input事件
    		var val = this.vm[exp]	// 获取属性值
    		node.value = val	 // 更新到页面中
    		new Watcher(this.vm, exp, value=>{ // 新的订阅者(绑定更新函数即可)
        		node.value = value
    		})
    		
    		node.addEventListener('input', e => {
	        let newVal = e.target.value
	        if (val == newVal) {
	            return false
	        }
	        this.vm[exp] = newVal
	        val = newVal
	    }, false)
    },
}

二、Vue类的创建

添加上监听器,并对DOM进行指令解析,根据不同指令添加不同的操作(例如事件处理、订阅服务…)

function Vue(options) {
	this.el = options.el
	this.data = options.data
	this.methods = options.methods

	Object.keys(this.data).forEach(key => { // 数据代理
		this.proxyKeys(key)
	})
	new Observer(this.data) // 监听器	
	new Compiler(this, this.el) // DOM解析
}

Vue.prototype = {
	proxyKeys(key) {
		var self = this
		Object.defineProperty(this, key, {
			enumerable: false,
			configurable: true,
			get() {
				return self.data[key]
			},
			set(newVal) {
				self.data[key] = newVal
			}
		})
	},
}

三、测试

代码参考: https://github.com/iphone3/VueDataBinging

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
		
	</head>
	
	<body>
		<div id="app">
			<h1 v-on:click="onAlert">Vue双向数据绑定之原理及实现</h1>
			<h3>名字: {{name}}</h3>
			
			<div>
				请输入你的名字:<input type="text" class="text" v-model='name' />
			</div>
		</div>
		
		<script src="dep.js" type="text/javascript" charset="utf-8"></script>
		<script src="watcher.js" type="text/javascript" charset="utf-8"></script>
		<script src="observer.js" type="text/javascript" charset="utf-8"></script>
		<script src="compiler.js" type="text/javascript" charset="utf-8"></script>
		<script src="Vue.js" type="text/javascript" charset="utf-8"></script>
		
		<script type="text/javascript">
			var app = new Vue({
				el:'#app',
				data: {
					name: 'atom'
				}, 
				methods: {
					onAlert: function(){
						alert('恭喜你完成了学习!!!')
					}
				}
			})
		</script>
	</body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值