vue源码解析之,插值替换
。
通过一个小例子 看一下,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="../node_modules/vue/dist/vue.js"></script> </head> <body> <div id="root"> <p>{{name}}</p> <p>{{message}}</p> </div> <div id="a"></div> <script> console.log(root); let app = new Vue({ el:"#root", data:{ name:"哈哈", message:"是一个男人" } }) console.log(root); </script> </body> </html>
下面我们模仿一下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> </head> <body> <div id="root"> <div> <div> <p>{{name}}--{{message}}</p> </div> </div> <p>{{name}}</p> <p>{{message}}</p> </div> <script> let rkuohao = /\{\{(.+?)\}\}/g;//匹配双括号的正则 .+? 两面加小括号 目的是 通过正则可以把它取出来 /** * 步骤拆解 * 1、拿到模板 * 2、拿到数据data * 3、将数据与模板结合,得到的是html元素(DOM元素) * 4、放到页面当中 */ // 第1步 let tmpNode = document.querySelector("#root"); // 第2步 let data = { name:"姓名", message:"是一个男人" } // 第3步 // 递归的方法去找到需要替换的节点 // 在现在的案例中 template 是dom元素, // 在真正的vue源码中是DOM-》字符串-》虚拟DOM(vnode)-》真正的DOM function compiler( template,data ){ let childNodes = template.childNodes;//取出子元素 console.log(childNodes); for(let i=0;i<childNodes.length;i++){ let type = childNodes[i].nodeType;//判断是否为文本节点 1:元素 3:文本节点 if(type === 3){ // 是文本节点 // 可以判断 里面是有有{{}}的插值 let txt = childNodes[i].nodeValue;//该属性只有文本节点才有意义 // 有没有{{}} txt = txt.replace(rkuohao,function(_,g1){ let key = g1.trim(); let value = data[key]; // 将{{ xxx }} 替换成value return value; }) // 注意 现在这个txt和DOM元素是没有关系的 childNodes[i].nodeValue = txt; }else if(type === 1){ // 是元素,需要判断是都有子元素,判断是否要插值 compiler(childNodes[i],data);//递归 } } } console.log(tmpNode);//此时 插值还未被替换 let generatorNode = tmpNode.cloneNode(true);//拷贝一份模板 这里是DOM元素可以通过这样的方法 拷贝 compiler( generatorNode,data );//调用一下 console.log(generatorNode);//此时 模板中的 插值就被替换成真正的值了 // 我们此时是没有生成新的template,所以这里看到的,是直接在页面中就更新了数据,因为DOM是引用类型 // 这样做模板就没有了 // 第4步 root.parentNode.replaceChild(generatorNode,root);//替换子元素 用新的元素去替换旧的元素 // 上面的案例是一个极简的 例子 存在很大的问题 // 1、vue使用的是虚拟DOM // 2、上面只考虑到了单属性 {{name}} 在真正的vue中会有很多这样的写法 {{child.name.firstName}} // 3、代码没有整合 vue是用构造函数的形式整合的 </script> </body> </html>
上面的小例子 以极简的方式 模仿vue 实现了 插值替换,代码显得有点low,我们整合一下,也模仿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> </head> <body> <div id="root"> <div> <div> <p>{{name}}--{{message}}</p> </div> </div> <p>{{name}}</p> <p>{{message}}</p> </div> <script> let rkuohao = /\{\{(.+?)\}\}/g;//匹配双括号的正则 .+? 两面加小括号 目的是 通过正则可以把它取出来 function compiler( template,data ){ let childNodes = template.childNodes;//取出子元素 for(let i=0;i<childNodes.length;i++){ let type = childNodes[i].nodeType;//判断是否为文本节点 1:元素 3:文本节点 if(type === 3){ // 是文本节点 // 可以判断 里面是有有{{}}的插值 let txt = childNodes[i].nodeValue;//该属性只有文本节点才有意义 // 有没有{{}} txt = txt.replace(rkuohao,function(_,g1){ let key = g1.trim(); let value = data[key]; // 将{{ xxx }} 替换成value return value; }) // 注意 现在这个txt和DOM元素是没有关系的 childNodes[i].nodeValue = txt; }else if(type === 1){ // 是元素,需要判断是都有子元素,判断是否要插值 compiler(childNodes[i],data);//递归 } } } function JGVue( options ){ // 习惯:内部的数据使用下划线开头 ,只读数据使用$开头 this._data = options.data; this._el = options.el; // 准备工作 (准备模板) this.$el = this._templateDOM = document.querySelector( this._el ); this.parentNode = this._templateDOM.parentNode;//存一下 模板的父级元素 // 渲染工作 this._render(); } // 在原型中 加上_render方法 // 作用 将模板结合数据 得到HTML 加到页面中 JGVue.prototype._render = function(){ this.compiler(); } /**编译 将模板与数据结合 得到真正的 DOM 元素 */ JGVue.prototype.compiler = function(){ let realHTMLDOM = this._templateDOM.cloneNode( true );//用模板拷贝 得到一个DOM compiler(realHTMLDOM, this._data); this.update( realHTMLDOM ); } /** 将DOM元素 放到页面中 */ JGVue.prototype.update = function( real ){ this.parentNode.replaceChild(real,document.querySelector("#root"));//将父元素的 新的子元素替换成 旧的 子元素 } // 想想怎么用 let app = new JGVue({ el:"#root", data:{ name:"哈哈", message:"时隔男人" } }) </script> </body> </html>
接下来处理路径 访问对象成员 也就是在 双括号中可以出现 xxx.xxx.xxx的形式的值
看红色部分的更改
<!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="../node_modules/vue/dist/vue.js"></script> </head> <body> <div id="root"> <p>{{name.firstName}}{{name.lastName}}{{age}}</p> </div> <script> // 要解决一个问题 使用xxx.xxx.xxx可以访问一个对象 // 用字符串路径来访问对象的成员(思路) function getValueByPath( obj,path ){ let paths = path.split("."); let res = obj; let prop; while( prop = paths.shift()){//把数组中的第0项取出来 res = res[ prop ]; } return res; } let rkuohao = /\{\{(.+?)\}\}/g;//匹配双括号的正则 .+? 两面加小括号 目的是 通过正则可以把它取出来 function compiler( template,data ){ let childNodes = template.childNodes;//取出子元素 for(let i=0;i<childNodes.length;i++){ let type = childNodes[i].nodeType;//判断是否为文本节点 1:元素 3:文本节点 if(type === 3){ // 是文本节点 // 可以判断 里面是有有{{}}的插值 let txt = childNodes[i].nodeValue;//该属性只有文本节点才有意义 // 有没有{{}} txt = txt.replace(rkuohao,function(_,g1){ let path = g1.trim(); let value = getValueByPath(data,path); // 将{{ xxx }} 替换成value return value; }) // 注意 现在这个txt和DOM元素是没有关系的 childNodes[i].nodeValue = txt; }else if(type === 1){ // 是元素,需要判断是都有子元素,判断是否要插值 compiler(childNodes[i],data);//递归 } } } function JGVue( options ){ // 习惯:内部的数据使用下划线开头 ,只读数据使用$开头 this._data = options.data; this._el = options.el; // 准备工作 (准备模板) this.$el = this._templateDOM = document.querySelector( this._el ); this.parentNode = this._templateDOM.parentNode;//存一下 模板的父级元素 // 渲染工作 this._render(); } // 在原型中 加上_render方法 // 作用 将模板结合数据 得到HTML 加到页面中 JGVue.prototype._render = function(){ this.compiler(); } /**编译 将模板与数据结合 得到真正的 DOM 元素 */ JGVue.prototype.compiler = function(){ let realHTMLDOM = this._templateDOM.cloneNode( true );//用模板拷贝 得到一个DOM compiler(realHTMLDOM, this._data); this.update( realHTMLDOM ); } /** 将DOM元素 放到页面中 */ JGVue.prototype.update = function( real ){ this.parentNode.replaceChild(real,document.querySelector("#root"));//将父元素的 新的子元素替换成 旧的 子元素 } // 想想怎么用 let app = new JGVue({ el:"#root", data:{ name:{ firstName:"张", lastName:"三丰" }, age:500 } }) </script> </body> </html>
到目前为止,我们以上提到了3个问题,1:构造函数 来整理代码,2、路径处理;3、vue中使用虚拟DOM,而我们用的是真是的DOM
现在就差第三个问题待解决了:
为什么需要用虚拟DOM呢?
主要为了提高性能;
如果我们一直在页面中操作DOM,就会导致极大的内存消耗,导致页面的刷新,降低性能等,所以vue采用了虚拟DOM;
使用虚拟DOM。所有的DOM操作都在内存中进行,只需要更新一遍,就可以;
。