vue的简单使用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> {{message}} <input v-model="modelData" />{{modelData}} </div> <script> /* 1.引入cdn 2.el是挂载点,就是所有vue指令、表达式都是需要在挂载点中使用 3.{{}} -> 表达式 4.v-model -> 指令,双向绑定; 问: vue的表达式是如何实现的呢? 你问过vue的双向绑定,如何实现的呢? */ let vm = new Vue({ el:"#app", data:{ message:"测试数据", modelData:"双绑数据" } }); </script> </body> </html>
模拟vue表达式的实现
/* 表达式的实现: 一、初次渲染 1.在作用域只能找到表达式 2.把message拿到,和数据进行查找,填充到表达式的位置 二、数据的响应 数据在更改的时候,视图也跟着更改 代码实现: 1.拿到数据,渲染到表达式里面 正则匹配到表达式,去数据里找表达式的数据进行替换 complle();//渲染视图函数 1.找到el(节点); 2.找到子节点 childNodes -> 伪数组 3.如何找到里面的内容呢? 转化伪数组 循环找每一个的 nodeType; 进行判断分离 -> 文本节点(1)与元素节点(3); 4.得到节点的值 node.textContent -> dfasdf{{message}}sadfasd 5.使用正则找到节点内容中的表达式 /\{\{\s*(\S+)\s*\}\}/g; 匹配得到的值 是否 包含正则; if(reg.test(textContent)){} 拿到正则匹配的内容 分组() let $1 = RegExp.$1; 6.替换文本的值 1.得到数据 this._data[$1]; 2.替换 node.textContent = textContent.replace(reg,this._data[$1]); 3.修正正则bug /\{\{\s*([^\s\{\}]+)\s*\}\}/g; 7.元素节点的渲染 -> 递归compileNode 思维总结:先找到作用域节点里面所有节点,区分文本节点和元素节点,判断文本节点有没有表达式(正则匹配),有表达式就找到下标内容,内容(message)对象的值,替换到表达式中;元素节点就重复一遍(递归); */ class Vue{ constructor(options){ this.options = options;//传进来的参数 this._data = this.options.data;//模范vue的_data this.complle(); } // 渲染视图 complle(){ let ele = document.querySelector(this.options.el);//大节点 this.compileNode(ele); } compileNode(ele){ let childNodes = ele.childNodes;//得到所有子节点 [...childNodes].forEach(node=>{ if(node.nodeType === 3){//文本节点; let textContent = node.textContent; //dfasdf{{message}}sadfasd let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g; if(reg.test(textContent)){//判断是否有正则匹配内容 -> test(); let $1 = RegExp.$1;//message node.textContent = textContent.replace(reg,this._data[$1]); } }else if(node.nodeType === 1){//元素节点; if(node.childNodes.length>0){ this.compileNode(node); } } }) } }
模仿vue数据改变视图也改变
数据劫持defineProperty
{ /* 参数: 对象(空对象|对象)、key(对象名称)、配置 一些配置: get(){} : 当访问的时候触发 set(newValue){} : 当修改的时候触发 -> newValue = 修改后的值 configurable:true : 配置 enumerable:true : for in */ // 新建defineProperty()属性 let obj = Object.defineProperty({},"name",{ configurable:true,//是否可以配置 -> 默认不可以配置 -> 不能执行删除操作 enumerable:true,//for in等一些操作 -> 默认不可操作 get(){ console.log("get..."); return "张三"; // get 里面需要return; }, set(newValue){ console.log("set... -> ",newValue); } }); // obj.name; // obj.name = "李四"; // 默认不能配置 // delete obj['name']; // console.log(obj); // 默认不可for in // for(let i in obj){ // console.log(obj[i]); // } } { // 修改defineProperty属性 let obj = { name:'张三', get:'李四' } // 可以为它设置 Object.defineProperty(obj,"name",{ configurable:true, enumerable:true, get(){ console.log("get..."); return "李四"; }, set(newValue){ console.log("set... -> ",newValue); } }); console.log(obj); // 缺点,当有多个的时候 需要进行循环; } { /* es6 Proxy 不需要进行循环 参数: 1.obj 2.配置 */ let obj = { name:"张三", age:20 } let newObj = new Proxy(obj,{ get(traget,key){ console.log("get..."); return traget[key]; }, set(traget,key,newValue){ console.log("set...->",newValue); } }); newObj.name = "lisi"; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./myvue.js"></script> </head> <body> <div id="app"> dfasdf{{message}}s{{message}}adfasd <div> {{message}}asdfadf <div></div> </div> <input v-model="myData" type="text">{{myData}} </div> <script> let vm = new Vue({ el:"#app", data:{ message:"测试数据", myData:"我的数据" } }); console.log(vm._data); vm._data.message = 2; </script> </body> </html>
/* 表达式的实现: 一、初次渲染 1.在作用域只能找到表达式 2.把message拿到,和数据进行查找,填充到表达式的位置 二、数据的响应 数据在更改的时候,视图也跟着更改 代码实现: 1.拿到数据,渲染到表达式里面 正则匹配到表达式,去数据里找表达式的数据进行替换 complle();//渲染视图函数 1.找到el(节点); 2.找到子节点 childNodes -> 伪数组 3.如何找到里面的内容呢? 转化伪数组 循环找每一个的 nodeType; 进行判断分离 -> 文本节点(1)与元素节点(3); 4.得到节点的值 node.textContent -> dfasdf{{message}}sadfasd 5.使用正则找到节点内容中的表达式 /\{\{\s*(\S+)\s*\}\}/g; 匹配得到的值 是否 包含正则; if(reg.test(textContent)){} 拿到正则匹配的内容 分组() let $1 = RegExp.$1; 6.替换文本的值 1.得到数据 this._data[$1]; 2.替换 node.textContent = textContent.replace(reg,this._data[$1]); 3.修正正则bug /\{\{\s*([^\s\{\}]+)\s*\}\}/g; 7.元素节点的渲染 -> 递归compileNode 思维总结:先找到作用域节点里面所有节点,区分文本节点和元素节点,判断文本节点有没有表达式(正则匹配),有表达式就找到下标内容,内容(message)对象的值,替换到表达式中;元素节点就重复一遍(递归); */ /* class Vue{ constructor(options){ this.options = options;//传进来的参数 this._data = this.options.data;//模范vue的_data this.complle(); } // 渲染视图 complle(){ let ele = document.querySelector(this.options.el);//大节点 this.compileNode(ele); } compileNode(ele){ let childNodes = ele.childNodes;//得到所有子节点 [...childNodes].forEach(node=>{ if(node.nodeType === 3){//文本节点; let textContent = node.textContent; //dfasdf{{message}}sadfasd let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g; if(reg.test(textContent)){//判断是否有正则匹配内容 -> test(); let $1 = RegExp.$1;//message node.textContent = textContent.replace(reg,this._data[$1]); } }else if(node.nodeType === 1){//元素节点; if(node.childNodes.length>0){ this.compileNode(node); } } }) } } */ /* 劫持数据: 1.为_data添加get()和set(); -> observer() 2.for in循环每一个key有define... 注意get的return; 3.当数据修改渲染视图: 一、把设置的值改到视图中 -> 自定义事件 1.继承自定义事件 2.通过自定义事件传新值 视图渲染; 3.使用正则替换内容 let newValue = e.detail; let oldValue = this._data[$1]; let reg = new RegExp(oldValue,"g"); node.textContent = node.textContent.replace(reg,newValue); class Vue extends EventTarget { constructor(options) { super(); this.options = options;//传进来的参数 this._data = this.options.data;//模范vue的_data this.observer(this._data); this.complle(); } observer(data) { for (let key in data) { let value = data[key]; let _this = this; Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { return value; }, set(newValue) { // console.log("set... -> ",newValue); let event = new CustomEvent(key, { detail: newValue }); _this.dispatchEvent(event); value = newValue; } }) } } // 渲染视图 complle() { let ele = document.querySelector(this.options.el);//大节点 this.compileNode(ele); } compileNode(ele) { let childNodes = ele.childNodes;//得到所有子节点 [...childNodes].forEach(node => { if (node.nodeType === 3) {//文本节点; let textContent = node.textContent; //dfasdf{{message}}sadfasd let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g; if (reg.test(textContent)) {//判断是否有正则匹配内容 -> test(); let $1 = RegExp.$1;//message node.textContent = textContent.replace(reg, this._data[$1]); { this.addEventListener($1, e => { let newValue = e.detail; let oldValue = this._data[$1]; let reg = new RegExp(oldValue,"g"); node.textContent = node.textContent.replace(reg,newValue); }); } } } else if (node.nodeType === 1) {//元素节点; if (node.childNodes.length > 0) { this.compileNode(node); } } }) } } */ /* 发布订阅模式: class Vue extends EventTarget { constructor(options) { super(); this.options = options;//传进来的参数 this._data = this.options.data;//模范vue的_data this.observer(this._data); this.complle(); } observer(data) { for (let key in data) { let value = data[key]; // 实例化收集器: let dep = new Dep(); Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { if (Dep.traget) { // 触发的时候收集 dep.addSub(Dep.traget);//-> 等同于把实例化对象给了收集器 } return value; }, set(newValue) { // 当值修改的时候执行'通知'函数 dep.notifi(newValue); value = newValue; } }) } } // 渲染视图 complle() { let ele = document.querySelector(this.options.el);//大节点 this.compileNode(ele); } compileNode(ele) { let childNodes = ele.childNodes;//得到所有子节点 [...childNodes].forEach(node => { if (node.nodeType === 3) {//文本节点; let textContent = node.textContent; //dfasdf{{message}}sadfasd let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g; if (reg.test(textContent)) {//判断是否有正则匹配内容 -> test(); let $1 = RegExp.$1;//message node.textContent = textContent.replace(reg, this._data[$1]); new Watcher(this._data,$1,(newValue) => { console.log(newValue); // 可以渲染视图了! let oldValue = this._data[$1]; let reg = new RegExp(oldValue,"g"); node.textContent = node.textContent.replace(reg,newValue); }); } } else if (node.nodeType === 1) {//元素节点; if (node.childNodes.length > 0) { this.compileNode(node); } } }) } } // 收集器 class Dep { constructor() { this.subs = [];//所有订阅者都放在这里面 } addSub(sub) {//有一个订阅者过来就执行一下 this.subs.push(sub); } // 通知; -> 通知每一个订阅者做一些事情 notifi(newValue) {//传值 // 触发update this.subs.forEach(sub => { sub.update(newValue); }); } } // 订阅者 class Watcher {//什么时候实例呢? 属性有多少就实例多少 -> message myData... constructor(data,key,cb) { this.cb = cb; Dep.traget = this;//静态属性; // 等同于触发了get(); data[key]; Dep.traget = null; } update(newValue) { // console.log("更新了..?"); // console.log(newValue); this.cb(newValue); } } */ /* 双向绑定 1.元素节点中处理: 2.找指令 循环attrs 判断v-开头的 let attrs = node.attributes; [...attrs].forEach(attr=>{ let attrName = attr.name; let attrValue = attr.value; if(attrName.indexOf("v-")==0){ } }) */ class Vue extends EventTarget { constructor(options) { super(); this.options = options;//传进来的参数 this._data = this.options.data;//模范vue的_data this.observer(this._data); this.complle(); } observer(data) { for (let key in data) { let value = data[key]; // 实例化收集器: let dep = new Dep(); Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { if (Dep.traget) { // 触发的时候收集 dep.addSub(Dep.traget);//-> 等同于把实例化对象给了收集器 } return value; }, set(newValue) { // 当值修改的时候执行'通知'函数 dep.notifi(newValue); value = newValue; } }) } } // 渲染视图 complle() { let ele = document.querySelector(this.options.el);//大节点 this.compileNode(ele); } compileNode(ele) { let childNodes = ele.childNodes;//得到所有子节点 [...childNodes].forEach(node => { if (node.nodeType === 3) {//文本节点; let textContent = node.textContent; //dfasdf{{message}}sadfasd let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g; if (reg.test(textContent)) {//判断是否有正则匹配内容 -> test(); let $1 = RegExp.$1;//message node.textContent = textContent.replace(reg, this._data[$1]); new Watcher(this._data,$1,(newValue) => { // 可以渲染视图了! let oldValue = this._data[$1]; let reg = new RegExp(oldValue,"g"); node.textContent = node.textContent.replace(reg,newValue); }); } } else if (node.nodeType === 1) {//元素节点; let attrs = node.attributes; [...attrs].forEach(attr=>{ let attrName = attr.name; let attrValue = attr.value; if(attrName.indexOf("v-")==0){ attrName = attrName.substr(2);//取消 v-; if(attrName === "model"){ node.value = this._data[attrValue]; // 监听input事件 node.addEventListener("input",e=>{ // console.log(e.target.value); let value = e.target.value; this._data[attrValue] = value; }) } } }) if (node.childNodes.length > 0) { this.compileNode(node); } } }) } } // 收集器 class Dep { constructor() { this.subs = [];//所有订阅者都放在这里面 } addSub(sub) {//有一个订阅者过来就执行一下 this.subs.push(sub); } // 通知; -> 通知每一个订阅者做一些事情 notifi(newValue) {//传值 // 触发update this.subs.forEach(sub => { sub.update(newValue); }); } } // 订阅者 class Watcher {//什么时候实例呢? 属性有多少就实例多少 -> message myData... constructor(data,key,cb) { this.cb = cb; Dep.traget = this;//静态属性; // 等同于触发了get(); data[key]; Dep.traget = null; } update(newValue) { // console.log("更新了..?"); // console.log(newValue); this.cb(newValue); } } /* 总结: 表达式是如何实现的? 1.通过正则找到花括号的表达式,渲染到视图 2.数据更新视图也更新: 通过数据劫持get和set,更新数据渲染到视图; 当然这两步也包含了很多知识点:正则,元素节点... 双向绑定是如何实现的? 1.通过元素节点判断 v- 开头 2.当取消v-的时候等于双向绑定的指令时,将数据渲染到input.value中,监听 input 的改动;修改数据;数据修改后视图自动渲染; */
来源:https://www.cnblogs.com/Afanadmin/p/12401946.html