zoukankan      html  css  js  c++  java
  • 学习手写vue,理解原理

    class Compiler{
    	constructor(el,vm){
    		// 判断el属性  是不是 一个元素, 如果不是就获取
    		this.el  = this.isElementNode(el)?el:document.querySelector(el);
    		// console.log(this.el);
    		
    		
    		this.vm = vm;
    		
    		// 把当前节点放到内存中
    		let fragment = this.node2fragment(this.el);
    		// console.log(fragment,"fragment")
    		
    		// 把节点中的内容进行替换
    		
    		// 用数据编译模板
    		this.compile(fragment)
    		
    		// 把结果再塞到页面中
    		this.el.appendChild(fragment);
    		
    	}
    	
    	isDirective(attrName){
    		return attrName.startsWith('v-');
    	}
    	// 编译元素的
    	compileElement(node){
    		let attributes = node.attributes;
    		[...attributes].forEach(attr=>{
    			let {name,value:expr} = attr;
    			// console.log(name,value,"attr");
    			// 判断是不是指令
    			if(this.isDirective(name)){
    				let [,directive] = name.split("-");
    				
    				let [directiveName,eventName] =  directive.split(":");  //v-on:click
    				CompileUtils[directiveName](node,expr,this.vm, eventName);
    			}
    		})
    	}
    	
    	// 编译文本的 
    	compileText(node){
    		let content = node.textContent;
    		// console.log(content)
    		if(/{{(.+?)}}/.test(content)){
    			console.log(content,"文本"); //找到所有文本
    			CompileUtils['text'](node,content,this.vm);
    		}
    	}
    	
    	// 用来编译内存中的dom节点   核心编译方法
    	compile(node){
    		let childNodes = node.childNodes; 
    		[...childNodes].forEach(child=>{
    			if(this.isElementNode(child)){
    				this.compileElement(child);
    				// console.log("element",child);
    				// 如果是元素  需要再次编译子节点
    				this.compile(child);
    			}else{
    				this.compileText(child);
    				// console.log("text",child)
    			}
    		})
    		// console.log(childNodes)
    	}
    	
    	
    	// 把节点移动到内存中
    	node2fragment(node){
    		// 创建一个文档碎片 
    		let fragment = document.createDocumentFragment();
    		let firstChild;
    		while(firstChild = node.firstChild){
    			// appendChild 具有移动性
    			fragment.appendChild(firstChild);
    		}
    		return fragment;
    	}
    	isElementNode(node){  //是不是元素节点
    		return node.nodeType === 1;
    	}
    }
    
    CompileUtils = {
    	// 根据表达式取到对应的数据
    	getVal(vm,expr){
    		return expr.split(".").reduce((data,current)=>{
    			return data[current];
    		},vm.$data)
    	},
    	
    	// 设置值
    	setValue(vm,expr,value){
    		expr.split(".").reduce((data,current,index,arr)=>{
    			if(index == arr.length-1){
    				data[current] = value;
    			}
    			return data[current];
    		},vm.$data)
    	},
    	// 解析v-model指令
    	model(node,expr,vm){  //node是节点  expr 是表达式 vm是实例
    		// 给输入框赋予value   node.value = xxx
    		let fn = this.updater['modelUpdater'];
    		
    		// 给输入框加一个观察者,如果数据更新了 会触发此方法,会拿新值给输入框赋值
    		new Watcher(vm,expr,(newVal)=>{
    			fn(node,newVal);
    		});
    		
    		node.addEventListener('input',(e)=>{
    			let value = e.target.value;  //获取用户输入的内容
    			this.setValue(vm,expr,value);
    		})
    		let value = this.getVal(vm,expr); 
    		fn(node,value);
    	},
    	html(node,expr,vm){
    		let fn = this.updater['htmlUpdater'];
    		
    		// 给输入框加一个观察者,如果数据更新了 会触发此方法,会拿新值给输入框赋值
    		new Watcher(vm,expr,(newVal)=>{
    			fn(node,newVal);
    		});
    		let value = this.getVal(vm,expr); 
    		fn(node,value);
    	},
    	
    	on(node,expr,vm,eventName){ //expr  就是 change
    		node.addEventListener(eventName,(e)=>{
    			vm[expr].call(vm,e);  //这里需要把  methods 绑定到 vm上
    		})
    	},
    	
    	getContentValue(vm,expr){
    		// 遍历表达式 将内容重新替换成完整的内容
    		return expr.replace(/{{(.+?)}}/g,(...args)=>{
    			return this.getVal(vm,args[1]);
    		})
    	},
    	text(node,expr,vm){
    		let fn = this.updater['textUpdater'];
    		let content = expr.replace(/{{(.+?)}}/g,(...args)=>{
    			
    			// 给表达式每个{{}}  都加上观察者
    			new Watcher(vm,args[1],()=>{
    				fn(node,this.getContentValue(vm,expr)); //返回一个全的字符串
    			})
    			return this.getVal(vm,args[1]);
    		});
    		fn(node,content);
    	},
    	updater:{
    		// 把数据插入到节点中
    		modelUpdater(node,value){
    			node.value = value;
    		},
    		htmlUpdater(node,value){  //xss攻击
    			node.innerHTML = value;
    		},
    		//处理文本节点
    		textUpdater(node,value){
    			node.textContent = value;
    		}
    	},
    	
    	
    }
    
    // 实现数据劫持的功能
    class Observer{
    	constructor(data){
    		console.log(data);
    		this.observe(data);
    	}
    	observe(data){
    		if(data && typeof data == "object"){
    			// 如果是对象
    			for(let key in data){
    				this.defineReactive(data,key,data[key]);
    			}
    		}
    	}
    	
    	defineReactive(obj,key,value){
    		this.observe(value);
    		let dep = new Dep(); //给每个属性都加上 发布订阅的功能
    		Object.defineProperty(obj,key,{
    			get(){
    				
    				// 创建watcher时 会取到对应的内容, 并且把watcher放到了全局上
    				Dep.target && dep.subs.push(Dep.target);
    				return value;
    			},
    			set:(newVal)=>{
    				if(newVal != value){
    					this.observe(newVal);
    					value = newVal;
    					dep.notify();
    				}	
    			}
    		})
    	}
    }
    
    // 观察者 (发布订阅)
    class Dep{
    	constructor() {
    	    this.subs = [];  //存放所有的watcher
    	}
    	// 订阅
    	addSub(watcher){  //添加watcher
    		this.subs.push(watcher);
    	}
    	// 发布
    	notify(){
    		this.subs.forEach(watcher=>watcher.update());
    	}
    	
    }
    class Watcher{
    	constructor(vm,expr,cb){
    		this.vm  = vm;
    		this.expr = expr;
    		this.cb = cb;
    		// 默认先存放一个老值
    		this.oldValue = this.get();
    	}
    	
    	get(){
    		Dep.target = this;  //先把自己放在Dep.target上
    		
    		// 取值,把这个观察者和数据关联起来
    		let value = CompileUtils.getVal(this.vm,this.expr);
    		Dep.target = null;  //不取消, 任何值取值  都会添加 watcher
    		return value;
    	}
    	update(){  //更新操作,数据变化后 会调用观察者的update方法
    		let newVal = CompileUtils.getVal(this.vm,this.expr);
    		if(newVal !== this.oldValue){
    			this.cb(newVal);
    		}
    	}
    }
    
    class Vue{
    	constructor(options){
    		this.$el = options.el;
    		this.$data = options.data;
    		
    		let computed = options.computed;
    		
    		let methods = options.methods;
    		
    		// 这个根元素 存在编译模板
    		if(this.$el){ 
    			
    			// 把数据全部转化成用Object.defineProperty来定义.
    			new  Observer(this.$data);
    			
    		
    			// 计算属性
    			for(let key in computed){  //有依赖关系
    				Object.defineProperty(this.$data,key,{
    					get:() => {
    						return computed[key].call(this);
    					}
    				})
    			}
    			
    			
    			// 绑定到vm上 进行代理 
    			for(let key in methods){
    				Object.defineProperty(this,key,{
    					get(){
    						return methods[key];
    					}
    				})
    			}
    			
    			//把数据获取操作  vm上的取值操作 都代理到 vm.$data
    			this.proxyVm(this.$data);
    			
    			console.log(this.$data,"$data")
    			new Compiler(this.$el,this);
    		}
    	}
    	// 实现可以通过vm取到对应的data中的值
    	proxyVm(data){
    		for(let key in data){
    			Object.defineProperty(this,key,{
    				get(){
    					return data[key];  //进行转化操作
    				},
    				set(newVal){  //设置代理方法
    					data[key] = newVal;
    				}
    			})
    		}
    	}
    }
    
    <!DOCTYPE html>
    <html>
    	<head>
    		<meta charset="utf-8">
    		<title></title>
    	</head>
    	<body>
    		<div id="app">
    			<input  v-model="school.name" />
    			{{school.name}}
    			<div>{{school.name}}</div>
    			<div>teacher:{{teacher.name}}</div>
    			<!-- 如果数据不变化 视图就不会刷新 -->
    			{{getNewName}}
    			
    			<button v-on:click="change">更新</button>
    			
    			<div v-html="message"></div>
    		</div>
    		<script src="./vue.js"></script>
    		<script>
    			let vm = new Vue({
    				el:'#app',
    				data:{
    					school:{
    						name:"chance",
    						age:30
    					},
    					teacher:{
    						name:'123'
    					},
    					message:"<h1>welcome</h1>"
    				},
    				methods:{
    					change(){
    						this.school.name = "希望小学"
    					}
    				},
    				computed:{
    					 getNewName(){
    						 return this.school.name + "附属学校"
    					 }
    				}
    			})
    		</script>
    	</body>
    </html>
    
    
  • 相关阅读:
    数据结构小练习
    【BZOJ 3652】大新闻 数位dp+期望概率dp
    【BZOJ 3326】[Scoi2013]数数 数位dp+矩阵乘法优化
    【Codeforces 506E】Mr.Kitayuta’s Gift&&【BZOJ 4214】黄昏下的礼物 dp转有限状态自动机+矩阵乘法优化
    【BZOJ 4455】 [Zjoi2016]小星星 容斥计数
    凸包小结
    Matrix-Tree定理题表
    PLAN OF HEOI(unfinished)
    (ex)BSGS题表
    exBSGS板子
  • 原文地址:https://www.cnblogs.com/chengyunshen/p/11971211.html
Copyright © 2011-2022 走看看