今天是周末,难得的没有加班,没有出去浪,最近感觉很是无聊呀,可能我成了隔夜的杯中水吧,没有落灰,可能中了一种毒,这种毒里边还有一种蔬菜,那就是香菇呀,哈哈哈,猜到了吧,中了好菇毒,闲下来还是研究一下前端的知识吧,毕竟学海无涯,不进则退,再者也应该为了以后打算,毕竟还是要为了以后买各种东西,包括什么车啊房啊,毕竟凡人一个还是需要这些俗物的,哈哈哈,生活不止眼前的苟且,还有诗和远方。今天就看看Vue3到底是怎么实现数据响应的,数据变了然后视图也变了。
-
Proxy数据代理和数据劫持
Proxy 也就是代理,可以帮助我们完成很多事情,例如对数据的处理,对构造函数的处理,对数据的验证,说白了,就是在我们访问对象前添加了一层拦截,可以过滤很多操作,而这些过滤,由你来定义。具体参考用法:
<div class="box"></div> <script> let data = { name:"xiaohua", age:25 } // 用法 const p = new Proxy(target, handler) //handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为 let p = new Proxy(data,{ set(target,prop,newValue){ console.log(targer,prop,newValue); return Reflect.set(...arguments); // Reflect 就是和Proxy配合 ,set将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。 }, get(target,prop){ console.log(target,prop,newValue); return Reflect.get(...arguments); //获取对象身上某个属性的值,类似于 target[name]。 } }) p.age = 21; // 改变属性值,调用了Proxy的set方法 console 出的是object(data),age,21(新值) console.log(p.name); // 调用了get ,console 出来的是21 </script>
-
实现数据渲染
想一下我们使用vue的时候,是不是数据data里边的变量,能使用 双花括号 写上变量的名称就可以渲染到浏览器上了呢,那么来看看到底是怎么实现的呢?那我们上边说的Proxy不可能随便说说的吧,怎么会呢?无事不登三宝殿,来吧用起来吧。
<div id="app"> {{name}} <div>{{message}}</div> </div> <script src = "myvue.js"></script> <script> let vm = new vue({ // 自己写一个小的vue 吧。激动么? el:"#app", data:{ name:"xiaohua", message:"带着小明" } }) </script>
class vue { constructor(option){ // 实例中的参数 this.option = option; this._data = this.option.data; this.el = document.querySelector(this.option.el); this.compileNode(this.el); } compileNode(el){ // 进行渲染的函数来了啊 let child = el.childeNodes; console.log(child); childe.forEach((node)=>{ // 用[...child]可以保证是数组 if(node.nodeType===3){ console.log("这是文本的节点"); let text = node.textContent; // 应该是一个{{name}}这样的东西 //进行匹配 let reg = /\{\{\s* ([^\s\{\}]+) \s*\}\}/; // 正则 [ ]表示取反,中间是 两端是{{ 但是中间不能再是{了 if(reg.test(text)){ let $1 = RegExp.$1; //匹配的第一个元素 也就是name //if(this._data[$1]) 写成下边这样 this._data[$1]&&(node.textContent=text.repalce(reg,this_data[$1])); } }else if(nodeType===1){ // 也就是节点元素 div或者其他 console.log("这是一个节点元素"); this.compileNode(node); } }) } }
-
双向数据绑定
我们渲染完数据了,那肯定想知道v-model这种双向数据绑定的了,不说下去我感觉自己都得好奇死,现在坐下来用小脑袋想一想,v-model 事件,1.要判定node上是不是有v-model属性,2.监听v-model的值的变化去改变data中的数据,3.监听一下(自定义一下这个事件)这个数据变化,再把这个渲染到页面有这个值的元素中
<div id="app"> <input type="text" v-model="test"> {{test}} </div> <script> let vm = new vue({ el:'#app', data:{ test:"双绑定数据" } }); console.log(vm) console.log(vm._data.name); </script>
class vue { constructor(option){ this.option = option; this._data = this.option.data; this.el = document.querySelector(this.option.el); this.compileNode(this.el); } compileNode(el){ let child = el.childeNodes; console.log(child); childe.forEach((node)=>{ if(node.nodeType===3){ let text = node.textContent; let reg = /\{\{\s* ([^\s\{\}]+) \s*\}\}/; if(reg.test(text)){ let $1 = RegExp.$1; this._data[$1]&&(node.textContent=text.repalce(reg,this_data[$1])); } }else if(nodeType===1){ // 1. 这是一个input 元素在这里完成第一步 let attr = node.attributes; // 获取所有节点属性对象 if(attr.hasOwnProperty('v-model') ){// 对象中是否含有属性为v-model的 let keyName = attr['v-model'].nodeValue; node.value = this.data_[keyName];//先把data中的数据放进input的value里边 属于渲染 // 2.监听一下input值的变化,改变data的值 node.addEventListener('input',e=>{ this._data[keyName] = node.value; }) } this.compileNode(node); } }) } }
// 3. 怎么监听data数值中的变化呢?那是不想到上边的数据劫持,正戏来了,开始的proxy该用上了,劫持一下数据 class vue extends EventTarget{ constructor(option){ super(); //进行继承 this.option = option; this._data = this.option.data; this.el = document.querySelector(this.option.el); this.compileNode(this.el); } observe(data){ let _this = this ; this._data = new Proxy(data,{ set(target,propertyKey,newValue){ console.log(propertyKey); // 对应data的test let event = new CustomEvent(propertyKey,{ // 自定义一个事件,在渲染的时候触发这个函数 detail:newValue }); _this.dispatchEvent(event); // 触发事件 注意this的指向 ,然后将这事件绑定到渲染{{}}的值的时侯 return Reflect.set(...arguments); } }) } }
// 查漏补缺一下 // 在学习一下自定义事件 ,要自定义事件必须继承一下自定义事件 EventTarget的方法 //extends是继承 EventTarget 是将会创建一个新的EventTarget 对象实例。 // 创建一个事件对象,名字为newEvent,类型为build var newEvent = new CustomEvent('build', { bubbles:true,cancelable:true,composed:true }); //给这个事件对象创建一个属性并赋值,这里绑定的事件要和我们创建的事件类型相同,不然无法触发 newEvent.name = "新的事件!"; //将自定义事件绑定在document对象上 document.addEventListener("build",function(){ alert("你触发了使用CustomEvent创建的自定义事件!" + newEvent.name); },false) //触发自定义事件 ,也叫做发布吧 document.dispatchEvent(newEvent);
-
完整代码
class vue extends EventTarget{ constructor(option){ super(); this.option=option; this._data = this.option.data; this.el = document.querySelector(this.option.el); this.observe(this._data) this.compileNode(this.el); } observe(data){ let _this = this; this._data = new Proxy(data,{ set(target,prop,newValue){ console.log(prop); let event = new CustomEvent(prop,{ detail:newValue }); _this.dispatchEvent(event); return Reflect.set(...arguments); } }) } compileNode(el){ let child =el.childNodes; console.log(child); child.forEach(node=>{ if (node.nodeType === 3) { let text = node.textContent; let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/; if (reg.test(text)) { let $1 = RegExp.$1; this._data[$1]&&(node.textContent = text.replace(reg,this._data[$1])); this.addEventListener($1,c=>{ //将上边自定义的函数,绑定到上边 node.textContent = text.replace(reg,c.detail); }) } }else if(node.nodeType===1){ let attr = node.attributes; console.log(attr); if (attr.hasOwnProperty('v-model')) { let keyName = attr['v-model'].nodeValue; node.value = this._data[keyName]; node.addEventListener('input',e=>{ this._data[keyName] = node.value; }) console.log(keyName); } this.compileNode(node); } }) } }