Moment For Technology

325 lines of code to understand vUE two-way data binding

Posted on Sept. 23, 2022, 1:37 p.m. by 朱志偉
Category: The front end Tag: vue.js
  • Simple VUe2 two-way data binding code, support parsing{{}}, v - bind, v - on commandTo supportMounted hook callback and Methodscall
let DataBing=function(dataoptions) {/*双向数据绑定*/
        let iswatch=false;//页面初始加载不执行监听
	function DataBinginit(options) { /*option:主调用函数传回来的参数对象*/
		let self = this;
		this.data = options.data.call(this); /*data对象集合*/
		this.methods = options.methods; /*methods:方法集合*/
		this.watchs = options.watchs; /*watchs:监听对象集合*/
		Object.keys(this.data).forEach(function(key) { /*遍历每一个属性*/
			self.proxyKeys(key); /*创建访问或修改data中的每一个属性的服务*/
		});
		observe(this.data); /*创建记录数据变化的服务*/
		new Compile(options.el, this); //模板编译
		iswatch=true;
		options.mounted.call(this); // 所有事情处理好后执行mounted函数
	}
	DataBinginit.prototype = {
		proxyKeys: function(key) {
			let self = this;
			//Object.defineProperty(obj, prop, descriptor)
			//obj: 需要被操作的目标对象
			//prop: 目标对象需要定义或修改的属性的名称
			//descriptor: 将被定义或修改的属性的描述符
			Object.defineProperty(this, key, {
				enumerable: false, //表示该属性是否可枚举,即是否通过for-in循环或Object.keys()返回属性,如果直接使用字面量定义对象,默认值为true
				configurable: true, //表示能否通过delete删除此属性,能否修改属性的特性,或能否修改把属性修改为访问器属性,如果直接使用字面量定义对象,默认值为true delete:
				/*Writable:false,*/ //当writable为false(并且configrubale为true),[[value]]可以通过defineeProperty修改, 但不能直接赋值修改
				get: function getter() { //一个给属性提供 getter 的方法(访问对象属性时调用的函数,返回值就是当前属性的值),如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined
					return self.data[key];
				},
				set: function setter(newVal) { //一个给属性提供 setter 的方法(给对象属性设置值时调用的函数),如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined
					self.data[key] = newVal;
				}
			});
		}
	}
	function Observer(data) {
		this.data = data; /*data*/
		this.walk(data);
	}
	Observer.prototype = {
		walk: function(data) {
			let self = this;
			Object.keys(data).forEach(function(key) {
				self.defineReactive(data, key, data[key]); //访问或修改data中的每一个属性
			});
		},
		defineReactive: function(data, key, val) {
			let dep = new Dep();
			let childObj = observe(val);
			Object.defineProperty(data, key, {
				enumerable: true,
				configurable: true,
				get: function getter() { //缓存实体,new出来的 dep数组用来保存数据
					if (Dep.target) {
						dep.addSub(Dep.target);
					}
					return val;
				},
				set: function setter(newVal) { /*更新缓存里的值(重新获取)*/
					if (newVal === val) {
						return;
					}
					val = newVal;
					dep.notify();
				}
			});
		}
	};
	function observe(value, vm) {
		if (!value || typeof value !== 'object') {
			return;
		}
		return new Observer(value);
	};
	function Dep() {
		this.subs = [];
	}
	Dep.prototype = {
		addSub: function(sub) {
			this.subs.push(sub); /*缓存*/
		},
		notify: function() {
			this.subs.forEach(function(sub) {
				sub.update(); /*缓存更新*/
			});
		}
	};
	/*编译展现*/
	function Compile(el, vm) {
		this.vm = vm;
		this.el = document.querySelector(el); /*当前模板dom对象*/
		this.fragment = null;
		this.init();
	}
	Compile.prototype = {
		init: function() {
			if (this.el) {
				this.fragment = this.nodeToFragment(this.el); //创建虚拟dom
				this.compileElement(this.fragment);
				this.el.appendChild(this.fragment);
			} else {
				console.log('Dom元素不存在');
			}
		},
		nodeToFragment: function(el) {
			let fragment = document.createDocumentFragment();
			let child = el.firstChild;
			while (child) {
				// 将Dom元素移入fragment中
				fragment.appendChild(child);
				child = el.firstChild
			}
			return fragment;
		},
		compileElement: function(el) {
			let childNodes = el.childNodes;
			let self = this;
			[].slice.call(childNodes).forEach(function(node) {
				let reg = /\{\{(.*)\}\}/; //用来判定是否为 {{*}} ;
				let text = node.textContent; //当前node(选中dom)的文本内容(就是要显示的文本等)
				if (self.isElementNode(node)) { //dom
					self.compile(node);
				} else if (self.isTextNode(node)  reg.test(text)) { //文本
					self.compileText(node, reg.exec(text)[1]);
				}
				if (node.childNodes  node.childNodes.length) { //递归调用
					self.compileElement(node);
				}
				
			});
		},
		compile: function(node) {
			let nodeAttrs = node.attributes; //用户属性集合
			let self = this;
			Array.prototype.forEach.call(nodeAttrs, function(attr) {
				let attrName = attr.name;
				if (self.isDirective(attrName)) { /*过滤用户属性保留V-指令*/
					let exp = attr.value; //获取 用户v-指令的值
					let dir = attrName.substring(2);
					if (self.isEventDirective(dir)) { // 是否为事件指令
						self.compileEvent(node, self.vm, exp, dir); //执行对应事件
					}else if(self.isattrDirective(dir)){
						dir=dir.substring(6);
						self.compileAttr(node, self.vm, exp, dir); /*v-model:属性 数据绑定编译处理*/
					}else { // 为v-model 指令
						self.compileModel(node, self.vm, exp, dir); /*v-model 数据绑定编译处理*/
					}
					node.removeAttribute(attrName);
					
				}
			});
		},
		compileText: function(node, exp) {
			let self = this;
			let initText = this.vm[exp];
			this.updateText(node, initText);
			new Watcher(this.vm, exp, function(value) {
				self.updateText(node, value);
				self.compilewatch(node, self.vm, exp);
			});
		},
		compileEvent: function(node, vm, exp, dir) {/*事件*/
		    let self = this;
			let eventType = dir.split(':')[1];
			let ev=self.getargs(exp);
			let args='';
			if(ev){
				args=ev;
				let zk=exp.indexOf('(');
				exp=exp.substr(0,zk)
			}
			let cb = vm.methods  vm.methods[exp];
			if (eventType  cb) {
				node.addEventListener(eventType,cb.bind(vm,args), true);
				//node.addEventListener(eventType, cb.apply(vm,args), false);
				//node.addEventListener(eventType,cb.call(vm,'name','age'), false);
			}
		},
		getargs:function(exp){
			let reg = /\((.*)\)/; 
			let args;
			if(reg.test(exp)){
				args=reg.exec(exp)[1].split(",");
				let reg1 = /\"(.*)\"/,
				    reg2 = /\'(.*)\'/;
				for(let i in args){
					if(reg1.test(args[i])){args[i]=reg1.exec(args[i])[1]}
					if(reg2.test(args[i])){args[i]=reg2.exec(args[i])[1]}
				}
			}
		    return args;
		},
		compilewatch:function(node, vm, exp) {/*监听值改变*/
   		    let wt = vm.watchs  vm.watchs[exp];
			if (wtiswatch) {
	             wt.call(vm);
			}
		},
		compileModel: function(node, vm, exp, dir) {
			
			let self = this;
			let val = this.vm[exp];
			this.modelUpdater(node, val,"value"); /*数据赋给dom*/
			new Watcher(this.vm, exp, function(value) {
				self.modelUpdater(node, value,"value");
				self.compilewatch(node, self.vm, exp);
			});
			node.addEventListener('input', function(e) { //dom 值赋给数据
				let newValue = e.target.value;
				if (val === newValue) {
					return;
				}
				self.vm[exp] = newValue; //复制
				val = newValue;
			});
		},
		compileAttr: function(node, vm, exp, dir) {
			let self = this;
			let val = this.vm[exp];
			this.modelUpdater(node, val,dir); /*数据赋给dom*/
			new Watcher(this.vm, exp, function(value) {
				self.modelUpdater(node, value,dir);
				self.compilewatch(node, self.vm, exp);
			});
		},
		updateText: function(node, value) {
			node.textContent = typeof value == 'undefined' ? '' : value; //更新dom  {{*}}的值
		},
		modelUpdater: function(node, value, attr) {
			node[attr] = typeof value == 'undefined' ? '' : value; //更新 attr属性的值
		},
		isDirective: function(attr) {
			return attr.indexOf('v-') == 0;//指令
		},
		isEventDirective: function(dir) {
			return dir.indexOf('on:') === 0;//事件
		},
		isattrDirective: function(dir) {
			return dir.indexOf(':') === 4||dir.indexOf(':') === 5dir.indexOf('on:') === -1;//属性
		},
		isElementNode: function(node) {
			return node.nodeType == 1;
		},
		isTextNode: function(node) {
			return node.nodeType == 3;
		}
	}
	/*数据缓存*/
	Dep.target = null; /*用来缓存遍历当前this*/
	function Watcher(vm, exp, cb) {
		this.cb = cb;
		this.vm = vm;
		this.exp = exp;
		this.value = this.get(); // 获取缓存的当前属性的值
	}
	Watcher.prototype = {
		update: function() {
			this.run(); /*更新值*/
		},
		run: function() {
			let value = this.vm.data[this.exp]; /*获取实时的值*/
			let oldVal = this.value;
			if (value !== oldVal) {
				this.value = value;
				this.cb.call(this.vm, value, oldVal);
			}
		},
		get: function() {
			Dep.target = this; // 缓存自己
			let value = this.vm.data[this.exp] // 从缓存里获取当前属性的值
			Dep.target = null; // 释放自己
			return value;
		}
	};
	return new DataBinginit(dataoptions);
}
Copy the code
  • extensionDate.prototype.formatsupportDate.format Formats the DateThe calling code
Function getNowDateStr() {return yyyY-MM-DD hh: MM :ss*/ let me = this; let date = new Date(); Format (" Current time: YYYY-MM-DD, week W, quarter Q, hh: MM :ss:c"); return date.format(" Current time: YYYY-MM-DD, week W, quarter Q, hh: MM :ss:c"); } Date.prototype.format = function (format) { //new Date( year, month, date, hrs, min, SEC) //new Date() // The argument can be an integer or a string, but the format must be correct. New Date(2009,1,1) // correct new Date("2009/1/1") // correct //example new Date(). Format (" current Date: yyyy-mm-dd, week W, yyyy-mm-dd, time: hh:mm:ss:c") let o = { "Y+": this.getFullYear() + '', "M+": this.getMonth() + 1, //month MM "D+": this.getDate(), //day DD "h+": this.getHours(), //hour hh "m+": this.getMinutes(), //minute mm "s+": This.getseconds (), //second ss "Q+": Math. Floor ((this.getMonth() + 3) / 3), //quarter Q "c+": This.getmilliseconds (), //millisecond c "W": [' one ', 'two ',' three ', 'four ',' five ', 'six ', [this.getDay() -1] //week week} for (let k in o) {if (new RegExp("(" + k + ")).test(format)) {format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr((("" + o[k]).length = 2 ? 2 : ("" + o[k]).length))) } } return format }Copy the code
  • Page calls
 HTML lang="cn" head meta charset=" utF-8 " title /head style #app {text-align: center; } /style body div id="app" h2{{title}}/h2 ! --input v-model="name"-- input ID ="input" V-model ="name" placeholder=" placeholder"  h1  {{name}}  / h1   h1  {{name1}}  / h1   h1  the current time for  span  {{date}}  / span   / h1   button v - on: click = "clickMe (' 1 ', '2')"  click me To clear the input field values! /button button V-on :click=" getVal " /button button v-on:click="clickto"click me try! /button /div /body script src="index.js"/script script type="text/javascript" new DataBing({ el: '#app', data(){return{title: 'Hello world! ', name: '333', name1: 'hahaha ', attrName: "attrname", date: getNowDateStr(),}}, methods: {clickMe: function (c1,c2) { var ev = window.event; this.name =''; this.attrname = 'attrname'; console.log(ev,c1,c2) }, clickto:function () { this.name = 'hello world'; This.attrname = 'test '+getNowDateStr(); this.attrName =' test '+getNowDateStr(); }, getval:function(){ alert(this.name); }}, watchs: {name:function(){console.log(getNowDateStr()+' +this.name '); }, attrname:function(){console.log(' input box name is: '+ this.attrname); } }, mounted: function () { window.setInterval(() = { this.date=getNowDateStr(); }, 1000); }}); /script /htmlCopy the code
  • vue3 proxyYou can use proxy to implement a simple two-way data binding
// Proxy is used to modify the default behavior of certain operations, which is equivalent to making changes at the language level. Therefore, it is a kind of "meta programming", that is, programming the programming language. // Proxy can be understood as a layer of "interception" before the target object. All external access to the object must pass this layer of interception. Therefore, it provides a mechanism for filtering and rewriting external access. The word Proxy is used to mean that it acts as a Proxy for certain operations. // Proxy supports 13 interception operations. // get(target, propKey, receiver) : intercepts reading of object properties, such as proxy.foo and proxy['foo']. // Set (target, propKey, value, receiver) : Intercepts the setting of object properties, such as proxy.foo = v or proxy['foo'] = v, and returns a Boolean value. // has(target, propKey) : intercepts the propKey in proxy operation and returns a Boolean value. // deleteProperty(target, propKey) : intercepts the operation of delete Proxy [propKey] and returns a Boolean value. / / ownKeys (target) : interception Object. GetOwnPropertyNames (proxy), Object. GetOwnPropertySymbols (proxy), the Object. The keys (proxy), for... The in loop returns an array. This method returns the property names of all of the target Object's own properties, whereas object.keys () returns only the traversable properties of the target Object itself. / / getOwnPropertyDescriptor (target, propKey) : interception Object. GetOwnPropertyDescriptor (proxy, propKey), returns the attributes describe objects. // defineProperty(target, propKey, propDesc) : Intercepts Object.defineProperty(proxy, propKey, propDesc), Object.defineProperties(proxy, propDescs), and returns a Boolean value. // preventExtensions(target) : Intercepts object.preventExtensions (proxy), returns a Boolean. GetPrototypeOf (target) : Intercepts object.getProtoTypeof (proxy) and returns an Object. // isExtensible(target) : Intercepts Object.isextensible (proxy), returning a Boolean value. // setPrototypeOf(target, proto) : Intercepts Object.setProtoTypeof (proxy, proto) and returns a Boolean value. If the target object is a function, there are two additional operations that can be intercepted. // apply(target, object, args) : intercepts operations that Proxy instances call as functions, such as Proxy (... The args), proxy. Call (object,... The args), proxy. Apply (...). . Construct (target, args) : intercepts operations called by Proxy instances as constructors, such as new Proxy (... The args). // Trick: One trick is to set the Proxy object to the object. Proxy property so that it can be called on object objects. var object = { proxy: new Proxy(target, handler) }; var handler = { get: function(target, name) { console.log("get") if (name === 'prototype') { return Object.prototype; } return 'Hello, ' + name; }, set: function(target, thisBinding, args) { console.log("set") return 20; }, apply: function(target, thisBinding, args) { console.log("apply") return args[0]; }, construct: function(target, args) { console.log("construct") return { value: args[1] }; }}; var fproxy = new Proxy({}, handler); var fproxy2 = new Proxy(function(x, y) { return x + y; }, handler); let obj = Object.create(proxy); Fproxy2 (1, 2) // Although Proxy can Proxy the access to the target object, it is not the transparent Proxy of the target object, that is, without any interception, it cannot guarantee the behavior consistency with the target object. The main reason is that in the case of Proxy, the this keyword inside the target object points to Proxy. // Some internal attributes of native objects can only be obtained with the correct this, so Proxy cannot Proxy these attributes of native objects. This fixes the problem by binding the original object to this. const target = new Date('2015-01-01'); const handler = { get(target, prop) { if (prop === 'getDate') { return target.getDate.bind(target); } return Reflect.get(target, prop); }}; const proxy = new Proxy(target, handler); Proxy.getdate () // 1 // ES5 provides the object.defineProperty method, which can define a new property on an Object or modify an existing property of an Object and return the Object. DefineProperty (obj, prop, descriptor) // Parameters: // obj: the Object on which attributes are to be defined. // prop: The name of the property to define or modify. // Descriptor: Descriptor of attributes to be defined or modified. Var obj = {value: ""} var value =" "; Object.defineProperty(obj, "value", { get: function() { return value }, set: function(newVal) { value = newVal } }) document.querySelector('#input').oninput = function() { var value = this.value; obj.value = value; document.querySelector('#text').innerHTML = obj.value; } const data = {name: const data = {name: const data = {name: const data; Object.keys(data). ForEach (function(key) {object.defineProperty (data, key, {enumerable: true, configurable: true, get: function() { console.log('get'); }, set: function(newVal) {console.log(' I changed it to ${newVal} '); },})}) data.name = 'gg' // This is just a simple hijacking // disadvantage: Object.defineProperty first flaw, can't listen for array changes // difference: // Proxy can directly listen to objects instead of attributes // Proxy can directly hijack the entire Object and return a new Object, which is much better than Object.defineProperty in terms of convenience and underlying functionality. // Proxy can directly listen for array changes // Proxy has up to 13 interception methods, not limited to apply, ownKeys, deleteProperty, has, etc. Object. DefineProperty does not have.Copy the code
Search
About
mo4tech.com (Moment For Technology) is a global community with thousands techies from across the global hang out!Passionate technologists, be it gadget freaks, tech enthusiasts, coders, technopreneurs, or CIOs, you would find them all here.