spring el表达式解析_VUE源码解析

1、数据代理
js<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title></title>
		
	</head>
	<body>
		
		<script type="text/javascript" src="js/compile.js"></script>
		<script type="text/javascript" src="js/observer.js"></script>
		<script type="text/javascript" src="js/watcher.js"></script>
		<script type="text/javascript" src="js/mvvm.js"></script>
		<script type="text/javascript">
			const vm = new MVVM({
				el:'#test',
				data:{
					name:'feifei2'
				}
			})
			console.log(vm.name,vm);
			vm.name = 'xiaoxiao2';
		</script>
	</body>
</html>

mvvm.js

/*
 * 相当于VUE的构造函数
 */
function MVVM(options){
	//将配置对象保存到vm
	this.$options = options;//此时的this指向的是MVVM,后面将会传给vm,将指向vm
	var data  = this._data = this.$options.data;
	var that = this;
	
	//数据代理
	//实现 vm.xxx -> vm._data.xxx
	//遍历data中所有的属性
	Object.keys(data).forEach(function(key){   
		//对指定的属性实现代理
		that._proxy(key)
	})
	
	//对data中所有层次的属性通过数据劫持实现数据绑定
	observe(data,this);
	
	//   创建一个编译对象(解析模板)
	this.$compile = new Compile(options.el || document.body,this);
}

MVVM.prototype = {
	$watch:function(key,cb,options){
		new watcher(this,key,cb)
	},
	//数据代理,更新vm的数据          ----》 数据绑定,更新_data的数据,从而更新页面
	_proxy:function(key){
		var that = this;
		Object.defineProperty(that,key,{
			configurable:false,
			emumerable:true,
			get:function proxyGetter(){
				return that._data[key]
			},
			set:function proxySetter(newVal){
				that._data[key] = newVal;
			}
		})
	}
}

2、模板解析

compile.js

function Compile(el, vm) {
	//保存vm到compile对象
	this.$vm = vm;
	//将el对应的元素对象保存到compile对象中
	this.$el = this.isElementNode(el) ? el : document.querySelector(el);
    //如果有el元素
	if(this.$el) {
		//1.取出el元素中所有子节点保存到一个fragment对象中
		this.$fragment = this.node2Fragment(this.$el);
		//2.编译fragment中所有层次子节点
		this.init();
		//3.将编译好的fragment添加到页面的el元素中
		this.$el.appendChild(this.$fragment);
	}
}

Compile.prototype = {
	node2Fragment: function(el) {
		//1、创建空的容器fragment
		var fragContent = document.createDocumentFragment(),
			child;
			
		//将el中所有子节点转移到fragment	
		while(child = el.firstChild) {
			fragContent.appendChild(child);
		}
        //返回
		return fragContent;
	},
	init: function() {
		//编译容器中所有节点元素
		this.compileElement(this.$fragment)
	},
	compileElement: function(el) {
		//取出最外层的子节点
		
		var childNodes = el.childNodes;

		//保存compile对象
		var that = this;
		//遍历所有子节点(text/element)
		[].slice.call(childNodes).forEach(function(node) {
			//得到节点的文本内容
			var text = node.textContent;
			//正则匹配{{}},如{{name}}
			var reg = /{{(.*)}}/; //匹配大括号表达式
			//判断节点是否是一个元素节点
			if(that.isElementNode(node)) {
				//编译它(解析指令v-if等)---难点
				that.compile(node);
			//判断节点是否是大括号格式的文本节点	
			} else if(that.isTextNode(node) && reg.test(text)) {
				//编译大括号表达式文本节点---难点
				that.compileText(node, RegExp.$1);
			}
			
			//如果当前节点还有子节点,通过递归调用,实现所有层次节点的编译
			if(node.childNodes&&node.childNodes.length){
				that.compileElement(node);
			}
		})
	},
	compile: function(node) {
		//得到标签的所有属性

		var nodeAttrs = node.attributes;

		var that = this;
		//遍历所有属性
		[].slice.call(nodeAttrs).forEach(function(attr){
			//得到属性名,比如v-on:click
			var attrName = attr.name;
			//判断是否是指令属性
			if(that.isDirective(attrName)){
				//得到属性值   ,  表达式:show
				var exp =attr.value;
				//从属性名中得到指令名:on:click
				var dir = attrName.substring(2);
                  //判断是否是事件指令
				if(that.isEventDirective(dir)){//解析处理事件指令
					compileUtil.eventHandler(node,that.$vm,exp,dir);
				}else{//普通指令
					compileUtil[dir]&&compileUtil[dir](node,that.$vm,exp,dir);
				}
				
				//移除指令属性
				node.removeAttribute(attrName);
			}
		})
	},
	compileText:function(node,exp){
		compileUtil.text(node,this.$vm,exp);
	},
	isDirective:function(attr){
		return attr.indexOf('v-') == 0;
	},
	isEventDirective:function(dir){
		return dir.indexOf('on') === 0;
	},
	isElementNode:function(node){
		return node.nodeType == 1;
	},
	isTextNode:function(node){
		return node.nodeType == 3;
	}
}

//包含多个解析指令的方法的对象(指令处理集合)
var compileUtil ={
	//解析v-text  === {{}}
	text:function(node,vm,exp){
		this.bind(node,vm,exp,'text');
	},
	//解析v-html 
	html:function(node,vm,exp){
		this.bind(node,vm,exp,'html');
	},
	//解析v-model
	model:function(node,vm,exp){
		this.bind(node,vm,exp,'model');
		var that = this;
		var val =that._getVMVal(vm,exp);
		node.addEventListener('input',function(e){
			var newValue = e.target.value;
			if(val===newValue){
				return;
			}
			that._setVMVal(vm,exp,newValue);
			val = newValue;
		})
	},
	//解析v-class
	class:function(node,vm,exp){
		this.bind(node,vm,exp,'class');
	},
	
	bind:function(node,vm,exp,dir){
		//得到更新节点的函数

		var updaterFn = updater[dir+'Updater'];
		//检查函数是否存在&&调用函数更新节点
		updaterFn&&updaterFn(node,this._getVMVal(vm,exp));
		
		new Watcher(vm,exp,function(value,oldValue){
			updaterFn&&updaterFn(node,value,oldValue)
		})
		
	},
	//事件处理
	eventHandler:function(node,vm,exp,dir){
		//得到事件类型//名   click
		var eventType =dir.split(':')[1],
		//从methods中得到表达式所对应的函数(事件回调函数)
		fn  = vm.$options.methods&&vm.$options.methods[exp];
		if(eventType&&fn){
			//给节点绑定指定事件名和回调函数(强制绑定this为vm)的DOM事件监听
			node.addEventListener(eventType,fn.bind(vm),false);
		}
	},
	_getVMVal:function(vm,exp){
		var val = vm._data;
		exp = exp.split('.');
		exp.forEach(function(k){
			val = val[k];
		})
		return val;
	},
	_setVMVal:function(vm,exp,value){
		var val = vm._data;
		exp = exp.split('.');
		exp.forEach(function(k,i){
			//非最后一个key,更新val 的值
			if(i<exp.length-1){
				val = val[k]
			}else{
				val[k] = value;
			}
		})
		
	}
};

//包含多个更新节点的方法的工具对象
var updater = {
	//更新节点的textContent属性值
	textUpdater:function(node,value){
		node.textContent = typeof value == 'undefined' ? '':value;
	},
	//更新节点的innerHTML属性值
	htmlUpdater:function(node,value){
		node.innerHTML = typeof value == 'undefined'?'':value;
	},
	//更新节点的className
	classUpdater:function(node,value,oldValue){
		var className = node.className;
//		className = className.replace(oldValue,'').replace(/s$/,'');
//		
//		var space = className&&String(value)?' ':'';
		
		node.className =className +(className?' ':'') +value;
		
	},
	//更新节点的value属性值
	modelUpdater:function(node,value,oldValue){
		node.value = typeof value == 'undefined' ?'':value;
	}
}
3、数据绑定

8a110f251acc2d20ea28906dcb9ddb39.png
3、数据劫持

observer.js

function Observer(data){
	this.data = data;
	this.walk(data);
}

Observer.prototype = {
	walk:function(data){
		var that = this;
		Object.keys(data).forEach(function(key){
			that.convert(key,data[key]);
		})
	},
	convert:function(key,val){
		this.defineReactive(this.data,key,val)
	},
	defineReactive:function(data,key,val){
		var dep  = new Dep();
		var childObj = observe(val);
		
		Object.defineProperty(data,key,{
			enumerable:true,//可枚举
			configurable:false,//不能再define
			get:function(){
				if(Dep.target){
					dep.depend();
				}
				return val;
			},
			set:function(newVal){
				if(newVal === val){
					return;
				}
				val = newVal;
				//新的值是object的话,进行监听
				childObj = observe(newVal);
				//通知订阅者
				dep.notify();
			}
		})
	}
}


function observe(value,vm){
	if(!value  || typeof value !== 'object'){
		return
	}
	return new Observer(value);
}


var uid = 0;


//它的实例是什么时候创建的,初始化的时候给data的属性进行数据劫持时创建的
function Dep(){
	this.id = uid++;
	//多个订阅者数组
	this.subs = [];
}

Dep.prototype = {
	addSub:function(sub){
		this.subs.push(sub);
	},
	depend:function(){
		Dep.target.addDep(this);
	},
	removeSub:function(){
		var index = this.subs.indexOf(sub);
		if(index != -1){
			this.subs.splice(index,1);
		} 
	},
	notify:function(){
		this.subs.forEach(function(sub){
			sub.update();
		})
	}
}
Dep.target = null;
4、数据监听与更新,通知变化

watcher.js

function Watcher(vm,exp,cb){
	this.cb = cb;  //用于更新界面的回调
	this.vm = vm;  //vm
	this.exp = exp;  //对应的表达式
	this.depIds = {};  //相关的n个dep的容器对象
	this.value = this.get();  //当前	表达式对应的value
}

Watcher.prototype = {
	update:function(){
		this.run();
	},
	run:function(){
		var value = this.get();
		var oldVal = this.value;
		if(value != oldVal){
			this.value = value;
			this.cb.call(this.vm,value,oldVal);
		}
	},
	addDep:function(dep){
		if(!this.depIds.hasOwnProperty(dep.id)){
			dep.addSub(this);
			this.depIds[dep.id] = dep;
		}
	},
	get:function(){
		Dep.target = this;
		var value = this.getVMVal();
		Dep.target = null;
		return value;
	},
	getVMVal:function(){
		var exp = this.exp.split('.');
		var val = this.vm._data;
		exp.forEach(function(k){
		   val = val[k]	
		});
		return val;
	}
};

4、总结

f5beeaf02b39f297497376ed0c6b819c.png

Vue数据双向绑定是通过数据劫持结合* 发布订阅者模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
EL表达式 (详解) EL表达式 1、EL简介 1)语法结构 ${expression} 2)[]与.运算符 EL 提供.和[]两种运算符来存取数据。 当要存取的属性名称中包含一些特殊字符,如.或?等并非字母或数字的符号,就一定要使用 []。 例如: ${user.My-Name}应当改为${user["My-Name"] } 如果要动态取值时,就可以用[]来做,而.无法做到动态取值。例如: ${sessionScope.user[data]}中data 是一个变量 3)变量 EL存取变量数据的方法很简单,例如:${username}。它的意思是取出某一范围中名称为 username的变量。 因为我们并没有指定哪一个范围的username,所以它会依序从Page、Request、Session、 Application范围查找。 假如途中找到username,就直接回传,不再继续找下去,但是假如全部的范围都没有找到时, 就回传null。 属性范围在EL中的名称 Page PageScope Request RequestScope Session SessionScope Application ApplicationScope 4) 1--EL表达式用${}表示,可用在所有的HTML和JSP标签中作用是代替JSP页面中复杂的JAVA代码. 2--EL表达式可操作常量 变量 和隐式对象. 最常用的 隐式对象有${param}和${paramValues}. ${param}表示返回请求参数中单个字符串的值. ${paramValues}表示返回请求参数的一组 值.pageScope表示页面范围的变量.requestScope表示请求对象的变量. sessionScope表示会话 范围内的变量.applicationScope表示应用范围的变量. 3 -- 表示是否禁用EL语言,TRUE表示禁止.FALSE表示不禁 止.JSP2.0中默认的启用EL语言. 4-- EL语言可显示 逻辑表达式如${true and false}结果是false 关系表达式如${5>6} 结 果是false 算术表达式如 ${5+5} 结果是10 5--EL中的变量搜

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值