1. 程式人生 > >今天學習一下js實現vue.js中的雙向繫結

今天學習一下js實現vue.js中的雙向繫結

看完後,越來越感覺到JavaScript原生的強大了。

話不多,原始碼附上

實現效果圖:

HTML:

		<div id="app">
			<input type="text" zf-model="msg" />
			<input type="text" zf-model="name"/>
			{{msg}}	{{name}}
			<div>逍遙君</div>
			<div>{{name}}</div>
		</div>
		<div id="app_1">
			
		</div>

js:

		<script>
			//將資料和節點掛載在一起
			let obj = {msg:new ViewModel(''),name:new ViewModel('')};			
			
			function ViewModel(data){
				this.data = data; //this.data代表的是當前的值 (new一個對應一個)
				this.nodes = [];//放節點的盒子
			};
			
			ViewModel.prototype.bindNode = function(node){ //這裡要做的事就是把節點和資料繫結在一起(prototype可以讓你向物件新增屬性和方法)
				this.nodes.push(node);//陣列中新增input
//				console.log(this.nodes)
			};
			ViewModel.prototype.setVal = function(newVal){ //這裡要做的是將input的值賦給{{}}
				if(newVal !== this.data){
					this.data = newVal;
					this.update()
				};
			};
			ViewModel.prototype.getVal = function(){ //把該方法暴露,取得data的值
				return this.data
			}
			ViewModel.prototype.update = function(){ //更新方法 可以把節點依次渲染成想要的結果
				this.nodes.forEach(node=>{
					if(node.nodeType === 1 ){
						node.value = this.data;
					}else{
						node.textContent = node.my.replace(/\{\{([^}]*)}\}/g,function(){
							return obj[arguments[1]].data;
						})
					}
				})
			}

			function compile(el){
				let ele =document.querySelector(el); //取元素
				//我們不要直接操作節點 可能會導致頁面的迴流
				let fragment = document.createDocumentFragment() //建立一個文件碎片
				//取ele下的第一個元素	直到取完為止並且將內容放到文件碎片中
				let child;
				while(child = ele.firstChild){
					fragment.appendChild(child);
				};
				function replace(fragment){ //用來遞迴判斷是否有我們想要的標籤
				//				NodeList 類陣列 Array.prototype.slice.call					
					Array.from(fragment.childNodes).forEach(node=>{	//childNodes取他的兒子 forEach遍歷陣列
	
	//					console.log(node)
						//判斷node 節點是標籤 還是文字
						if(node.nodeType === 1){	//元素節點
	//						node.attributes; //取到節點上的所有屬性  這還是一個類陣列 
							Array.from(node.attributes).forEach(attr=>{
	//							console.dir(attr);  //console.dir 顯示一個物件的所有屬性和方法
								let {name,value} = attr;  //相當於 let name = attr.name; let value = attr.value
								if(name.includes('zf-')){	//array.includes是判斷數組裡是否包含某一元素,返回true和false
									obj[value].bindNode(node) //給對應的input新增上bindNode這個方法
									node.addEventListener("input",function(e){
										obj[value].setVal(e.target.value)
									})
								};
							});
							//取得節點上的所有屬性
						};
						let reg = /\{\{([^}]*)\}\}/g;
						let text = node.textContent; //取得node文字節點中的數值用來做正則判斷
													//test方法用來檢索字串中是否有匹配的文字,有返回true.否則false
						if(node.nodeType === 3 &&reg.test(text)){//文字節點
							text.replace(reg,function(){
//								console.log(arguments)  //獲取到函式中的所有引數
								node.my = text ; //自定義屬性   保留原有的值
								obj[arguments[1]].bindNode(node);//給對應的text新增上bindNode這個方法
							});
						};
						if(node.childNodes.length){
							replace(node); //如果有巢狀關係 繼續查詢
						};
					});
				};
				replace(fragment); //編譯後要呼叫update方法
				Object.keys(obj).forEach(key=>{ //Object.keys(),該方法返回一個數組,可以拿到物件的所有屬性
					obj[key].update();	
				})
				// 這裡操作資料  是不會導致頁面迴流
				ele.appendChild(fragment);
			};
			compile('#app')
		</script>